Czas na zrobienie kolejnego kroku i stworzenie kolejnej aplikacji, w której wykorzystamy zdobytą do tej pory wiedzę.
W tej serii wpisów pokażę Ci, jak stworzyć aplikację, która będzie posiadała w sobie sygnalizator świateł znajdujący się na skrzyżowaniach. Zobaczysz, że budowanie aplikacji jest proste i przyjemne 🙂 Aplikacja będzie wykorzystywała wiedzę, którą przedstawiałem do tej pory na blogu tak, żeby pokazać i utrwalić zdobytą wcześniej wiedzę.
Ok, a więc po kolei zaczynajmy.
Pierwszym krokiem jest stworzenie nowego projektu w VisualStudio. W tym celu wybierzmy sobie projekt WPF App, który będzie wykorzystywał .NET Core.
Następnie wybierzmy lokalizację naszego projektu oraz jego nazwę.
Ok. Mamy nasz nowo utworzony projekt 🙂
Zacznijmy od utworzenia szablonu naszego sygnalizatora świetlnego. W tym celu wykorzystamy kontrolkę Rectangle oraz 3 kontrolki Ellipse. Przeciągniemy nasze kontrolki z toolboxa.
Czas na wykorzystanie wiedzy dotyczącej klas. Stwórzmy więc klasę “Sygnalizator”, która będzie przechowywała stan naszego sygnalizatora oraz będzie pozwalała na zmianę jego stanu dzięki wykorzystaniu “metod”.
W projekcie klikamy prawym przyciskiem myszy i z menu kontekstowego wybieramy “Add”, a następnie “new class”
Po kliknięciu “ok” zostanie utworzony plik naszej klasy. Możemy go zobaczyć w Solution Explorer.
Stwórzmy teraz naszą klasę “Sygnalizator”. Zacznijmy od stworzenia trzech property SwiatloGorne, SwiatloSrodkowe, SwiatloDolne, które będą typu Color. Będą one przechowywać informacje o kolorze konkretnej lampy na sygnalizatorze świetlnym.
Dodatkowo zmieńmy typ naszej klasy na public, tak żeby była ona dostępna w naszym programie.
Kolejnym etapem będzie stworzenie konstruktora, w którym ustawimy światła na start.
Warto pomyśleć o jakieś zmiennej, która będzie nam wskazywać jaka pozycja świateł jest aktualnie ustawiona, tzn. czy mamy ustawione światło czerwone czy zielone oraz jakąś zmienną, która będzie nas informowała, że aktualnie następuje zmiana świateł np. z czerwonego na zielone.
W tym celu dodajmy nowe zmienne UstawieniaSygnalizatora oraz SwiatlaSaZmieniane.
Zmienna SwiatlaSaZmieniane będzie typu bool, ponieważ chcemy przechowywać w niej informacje, że światło jest zmieniane (true) albo nie jest zmieniane (false).
Drugą zmienną będzie UstawieniaSygnalizatora i tutaj wprowadzimy sobie nowy typ zmiennych, jakim jest enum.
Enum, czyli wartość enumerowana, w której możemy podać własne rodzaje parametrów. W tym celu stwórzmy nowy plik, który będzie nazywał się SygnalizatorPozycjaSwiatelEnum. Dopisek Enum na końcu będzie nas informował, że jest to Enum i nie jest wymagane, aby dodawać go na koniec. Równie dobrze możemy nasz typ nazwać SygnalizatorPozycjaSwiatel.
Zmieńmy teraz typ naszego pliku z class na enum oraz zmieńmy typ z private(pamiętaj brak modyfikatora dostępu oznacza, że mamy podstawowy modyfikator dostępu czyli private). W kolejnym etapie dopiszemy 2 parametry jakie będzie przyjmował nasz enum, czyli “CzerwoneSwiatlo”, “ZieloneSwiatlo”.
Wróćmy do naszej klasy Sygnalizator i dodajmy tam nasze 2 zmienne UstawieniaSygnalizatora i SwiatlaSaZmieniane.
Ustawmy teraz nasze 2 zmienne w konstruktorze, jakie mają mieć wartości na początek.
Dodajmy metodę, która będzie odpowiedzialna za zmianę świateł.
Jak widzisz użyłem tam na początek if, w którym sprawdzamy czy światła są aktualnie zmieniane, aby zabezpieczyć się przed sytuacją, gdzie w trakcie zmiany świateł chcemy wywołać zmianę świateł i żeby sygnalizator nie zgłupiał.
Następnie jeżeli światła nie są w trakcie zmiany, za pomocą konstrukcji Switch sprawdzamy w jakiej pozycji jest sygnalizator.
Jeżeli będzie on w pozycji światło czerwone nastąpi zmiana na światło czerwone i żółte, a po 1 sekundzie na zielone.
Za opóźnienie odpowiedzialna jest metoda Sleep z biblioteki Thread, w której podajemy ile milisekund chcemy zatrzymać wątek naszego programu.
Jak widzisz linia 40 i 55 są podkreślone, ponieważ nie ma dodanej naszej biblioteki do przestrzeni namespace na górze klasy. Żeby pozbyć się błędu można u góry dopisać ręcznie “using”, albo można użyć ikony żarówki, która podpowiada nam rozwiązanie naszego problemu.
Wybieramy i okazuje się, że using został dodany automatycznie w linii 4.
using System.Threading;
Wróćmy do naszego kodu. W momencie zmiany światła, zmieniamy ustawienia sygnalizatora na zielone światło oraz zmieniamy naszą zmienną zabezpieczającą proces zmiany świateł SwiatlaSaZmieniane z true na false (linia 46,47)
W liniach między 49 a 62 jest sytuacja analogiczna, gdzie zmieniane zostają światła z zielonego na czerwone.
Ok 🙂 Mamy już naszą klasę “Sygnalizator”, która posiada 3 zmienne odpowiadające za konkretne lampy w sygnalizatorze, zmienne prywatne, które mają nam pomagać w procesie ustawiania i zmiany świateł oraz metodę, która będzie odpowiedzialna za zmianę świateł.
Czas połączyć naszą część graficzną z kodem. W tym celu przechodzimy do pliku MainWindow.xaml.cs gdzie tworzymy zmienną typu Sygnalizator o nazwie MojSygnalizator oraz inicjujemy ją w konstruktorze MainWindow.
Dodajmy teraz mechanizm, który będzie co określony czas zmieniał nasze światła automatycznie.
W tym celu tworzymy sobie zmienną o nazwie Timer, która będzie pochodziła z biblioteki
System.Timers.Timer
Następnie dopisujemy metodę ChangeLights, gdzie umieścimy kod, który co 20 sekund będzie wywoływał metodę CzasUplynal, w której umieścimy naszą metodę MojSygnalizator.ZmienSwiatlo.
Tutaj ważna uwaga
Kod można pisać na wiele sposobów, ale warto stosować się do dobrych praktyk (o dobrych praktykach przygotuję osobny wpis).
Kod powinien być czytany jak wiersz lub książka przygodowa. Poniżej jest screen, który przedstawia 2 podejścia
Po prawej w konstruktorze mamy umieszczony kod, który inicjalizuje naszą zmienną MojSygnalizator, następnie inicjalizuje wygląd naszej aplikacji w kolejnej części (linie od 17 do 20) inicjalizuje zmienną Timer, następnie przypisuje do funkcji Elapsed metodę CzasUplynal, następnie ustawia interwał co ile ma się metoda powtarzać, następnie uruchamia Timer.
Po lewej mamy podejście, w której rozdzielamy na metody kawałki kodu, które można wydzielić, tzn.: w konstruktorze mamy umieszczony kod, który inicjalizuje naszą zmienną MojSygnalizator, następnie inicjalizuje wygląd naszej aplikacji, w kolejnej linijce wywołujemy metodę ZmienSwiatloCoOkresCzasu. Tym samym, jeżeli wrócisz do kodu za pół roku lub pokażesz go komuś innemu, będzie to rozwiązanie gdzie ktoś wystarczy, że przeczyta 3 linijki i będzie wiedział już o co chodzi, a jeżeli będzie potrzebował znać szczegóły implementacji poszczególnych metod, może je zacząć czytać. W podejściu po prawej stronie zobaczy on 4 linijki ustawiające jakiś Timer, gdzie nie jest nigdzie powiedziane dlaczego jest ustawiany jakiś Timer, do czego on jest używany. Jedyną wskazówką jest metoda CzasUplynal (czas upłynął), ale ona nie wyjaśnia co się stanie kiedy czas upłynie. W rozwiązaniu po prawej stronie czytanie kodu przez kogoś innego lub po jakimś czasie wracanie do niego powoduje, że ciężej jest czytać co się dzieje i analiza kodu staje się trudniejsza.
Dobrą praktyką jest więc rozdzielanie kodu na małe funkcjonalności i zamykanie ich w postaci metod(czyli akcji jakie możemy wykonać) oraz nazywanie tych metod w taki sposób, aby po przeczytaniu nazwy było wiadomo co dokładnie robi nasza metoda.
Kolejną cenną uwagą jest nazywanie wszystkiego w naszym kodzie w języku angielskim, ponieważ rozwijać go może praktycznie każdy inny programista lub programistka na świecie. Używając języka polskiego ograniczamy nasz kod.
Ja używam w naszym przykładzie polskiego języka w naszym kodzie, ponieważ dzięki temu mam nadzieję, że obniżam próg zrozumienia kodu, jeżeli jeszcze nie znasz angielskiego. Zależy mi na tym, żeby kod był dla Ciebie zrozumiały, a nie żeby był jeszcze w 100% poprawny i zgodny z zasadami czystego i jakościowego kodu.
Wróćmy do naszego łączenia wyglądu aplikacji z mechaniką aplikacji. W tym celu użyjemy mechanizmu jakim jest Binding, który jest wspierany w aplikacjach WPF.
Ok. Do pracy 🙂
Przejdźmy do pliku MainWindow.xaml.cs i dopiszmy w konstruktorze MainWindows linijkę
this.DataContext = MojSygnalizator;
Dzięki tej linijce nasze gui będzie połączone z naszą zmienną MojSygnalizator, w której znajdują się zmienne SwiatloGorne, SwiatloSrodkowe, SwiatloDolne.
Przejdźmy do pliku MainWindow.xaml i dodajmy do naszych 3 kontrolek Elipse zmienne Fill oraz dopiszmy jakie zmienne mają być brane pod uwagę.
Żeby wszystko zadziałało trzeba jeszcze dopisać do naszej klasy Sygnalizator mechanizm, który będzie informował nasz layout o tym, że coś się zmieniło i trzeba odświeżyć nasz layout. Przejdźmy do klasy Sygnalizator i dopiszmy, aby dziedziczył on po interfejsie INotifyPropertyChanged (o dziedziczeniu był w tym wpisie link). Jak widać w naszym kodzie wpisany interfejs podświetli się na czerwono, ale wiemy już jak radzić sobie z tym problemem, wystarczy dodać using.
Ale to nie wszystko, teraz trzeba zaimplementować metody jakie ten interfejs na nas wymusza. W tym celu również możemy skorzystać z pomocy jaką daje nam VisualStudio i ponownie wykorzystujemy naszą żółtą żarówkę wybierając opcję “Implement Interface”
Pojawiła nam się linijka, która jest odpowiedzialna za wywołanie eventu (zdarzenia), jeżeli wywołamy metodę NotifyPropertyChanged.
Dopiszmy teraz naszą metodę NotifyPropertyChanged. Do jej działania będziemy używać funkcji z biblioteki System.Runtime.CompilerServices dlatego dodamy ją do sekcji using.
Czas na małe zmiany w naszych właściwościach SwiatloGorne, SwiatloSrodkowe, SwiatloDolne. W momencie kiedy następuje zmiana wartości (jest wykonywany set) musimy poza zmianą wartości wywołać metodę NotifyPropertyChanged.
W tym celu dodajemy zmienne prywatne _swiatloGorne, _swiatloSrodkowe, _swiatloDolne oraz implementujemy metody get i set w SwiatloGorne, SwiatloSrodkowe, SwiatloDolne
Ok, to już wszystko 🙂
Teraz nasz sygnalizator będzie zmieniał światła co 20 sekund.
Wyjaśnię jeszcze implementacje get i set. Weźmy sobie property SwiatloGorne w momencie kiedy chcemy zmienić jej wartość następuje wywołanie set, w którym sprawdzamy czy nasza nowa wartość “value” jest inna od aktualnie ustawionej prywatnej zmiennej _swiatloGorne. Jeżeli nie, to nic nie robimy, bo nic się nie zmienia, ale jeżeli wartość jest inna, wtedy najpierw przypisujemy nową wartość do naszej zmiennej lokalnej _swiatloGorne, a następnie wywołujemy metodę NotifyPropertyChanged, która ma za zadanie wygenerować zdarzenie, że wartość została zmieniona. To zdarzenie zostanie przechwycone przez nasz layout, który będzie wiedział, że ma coś zmienić(w naszym przypadku wartość w property Fill).
Wiem, że dla osoby początkującej może być to porządna dawka wiedzy, którą trzeba na spokojnie przestudiować, ale uwierz mi, że warto 🙂 W razie problemów pamiętaj, że zawsze możesz do mnie napisać i wtedy chętnie pomogę Ci w ogarnięciu tematu 🙂
W kolejnym wpisie pokażę Ci ciekawe miejsce, gdzie możesz umieścić swój kod i pochwalić się nim całemu światu.