Pierwsze kroki OOP / Środowisko MS Visual

2

Spotkałem na swojej drodze ludzi, którzy totalnie nie rozumieli zasady działania i nie orientowali się w środowiskach wizualnie-obiektowej budowy aplikacji.. (można to tak nazwać?). Podstawowa znajomość C/C++, Pascala czy innego: taka osoba widzi programowanie przez pisanie (o zgrozo?) strukturalnego kodu linijka po linijce. Jeśli przychodzi moment utworzyć dowolną aplikację w Borland Builder / MS Visual to jeszcze jakoś sobie radzą rozpoczynając „empty project” (pusty projekt) albo „console application” (aplikacja konsolowa) – to do czego od początku swojej nauki przywykli. Pół biedy, jeśli rozumieją zasadę pisania obiektowych aplikacji. Gdy przychodzi moment wyświetlenia się formy pustego projektu.. i tu opiszę jak ugryźć ten moment.

Z góry ostrzegam, że post jest przydługawy. Jeśli faktycznie zależy Ci na tej wiedzy: poświęć ten czas, bo możesz się wiele ciekawego dowiedzieć, czego ja nie spotkałem niegdyś gdy sam to opanowywałem. To do dzieła!

Jeszcze aby ułatwić wam wyszukiwanie tego artykułu, kilka zdań kluczowych dla wyszukiwarek: Jak napisać aplikację okienkową w C#, jak programować w .NET, pierwsze aplikacje okienkowe…

Wstępnie

Przez ten mini tutorial spróbuję wytłumaczyć totalnemu laikowi jak rozpocząć tworzyć aplikacje okienkowe. Będę pracował na:

  • Windows 7 (każda rodzina od Windows XP wzwyż nie robi tutaj różnicy)
  • Microsoft Visual Studio 2008 (.NET 3.5) – Większej różnicy nie powinno być na starszych IDE (bez .NET), rodzinie Borland’a lub następców
  • Będziemy posługiwać się składnią C# (- jeśli znasz przynajmniej jakiś język C-podobny, nie będziesz miał problemu)
  • No i będziemy pisać w .NET Frameworku. Jednak będą tutaj takie podstawy – że na pewno poradzisz sobie przy starym WinAPI/MFC i pokrewnych, wciąż „elastynie” budowanych formach z pomocą komponentów, VCL itp.
  • W przypadku pisania w .NET Framework w systemie również musi zostać zainstalowany pakiet .NET Redistributable Package jeśli chcemy na nim uruchomić naszą aplikację

Tworzymy nowy projekt

Zaraz po odpaleniu IDE od Microsoftu dostajemy nic innego jak „Start Page„. Można z niego szybko dostać się do ostatnio przerabianych projektów, poczytać jakieś mało istotne pierdoły.. Skupmy się na stworzeniu nowego projektu. Możemy posłużyć się na szybko skrótem CTRL+SHIFT+N lub bardziej lakonicznie: File, New, Project.

Jak było zupełnie z założeniach, bierzemy się za projekt w C# – możesz wybrać dowolny inny wedle upodobań. Osobiście polecam startować z tym co ja, szczególnie jeśli planujemy wiązać przyszłość z platformą .NET. Z resztą, jeśli planujemy przerabiać ten tutorial z pomocą platformy .NET, to i C# wydaje się lepszym rozwiązaniem ze względu na „dostosowanie składni i języka” do tej platformy. Po za tym wszystkim znacznie przyjemniej się pisze z tą składnią, niż zabawa dodatkowymi operatorami w C++.

Z lewej części okna „project types” szukamy i rozwijamy „Visual C#„, jak już nie trudno się domyślić, wybieramy „Windows Form Application„. Jeszcze zanim klikniemy OK dobierzmy wersję platformy na której chcemy bazować (w tym tutorialu jest to bez różnicy, przynajmniej .NET 2.0), następnie nazwa projektu jak i lokacja. Całość może wyglądać np. tak:

W końcu potwierdzamy klikając OK, lub naciskając na klawiaturze ENTER. Po kilku/nastu sekundach mielenia naszego sprzętu w końcu dostaniemy pustą formę przed sobą. Powinno to wyglądać mniej więcej tak:

„Prawie Hello World”, kompilacja

Najlepiej w tym momencie wypróbować nasz czysty projekt – sprawdzimy w ten sposób, czy nasze IDE jest poprawnie zainstalowane, przy okazji sprawdzimy, czy już od początku nie występują jakieś błędy (bo nie powinny). Klikamy zieloną strzałkę play’a lub klawiaturowo: F5. Po kompilacji (pierwsza może chwilkę potrwać) powinniśmy w końcu zobaczyć nasz pierwszy, okienkowy program. Może nie jest to jeszcze nic w stylu „Hello World”, ale za chwilkę do tego dojdziemy ;-)

Jeśli nie wystąpiły błędy: klikamy magiczny, czerwony X w naszej okienkowej aplikacji. Nastąpi jej zamknięcie, wrócimy do środowiska i naszego projektu. Można również wykorzystać kwadrat, znak stop‚u aby natychmiast zabić aplikację – szczególnie jest to przydatne, jeśli akurat się zawiesiła z różnych przyczyn (zazwyczaj błędów programistycznych).

Jeśli jednak wystąpiły błędy: nie powinno się to zdarzyć. Najprawdopodobniej masz jakiś burdel w zainstalowanym środowisku (jeśli dopiero raczkujesz i przypadkowo nie dołączasz jakichś innych modułów które mogły na to wpłynąć, to najlepiej jeśli usuniesz i zainstalujesz na nowo środowisko). W przypadku bardziej „pro” młodych programistów: użyj google, znajdź przyczynę i to napraw.  Następnie czytaj dalej:

Jak może zauważyłeś: zmienił się nieco wygląd środowiska w czasie trwania aplikacji. Dzieje się tak, gdyż uruchamiamy (póki co) wersję testową naszej aplikacji, a całe środowisko zamienia się w tym momencie w debugger – gdy nasza aplikacja będzie rzucała błędami, będziemy mogli szybko znaleźć przyczynę i dojść do błędu.

Również w folderze naszego projektu pojawiło się kilka nowych plików i katalogów. Najciekawsze z pewnością będzie dla Ciebie dogrzebanie się do pliku wykonywalnego, skompilowanej binarki naszego projektu. Również pracując z projektem C# mamy „mniejszy” burdel solucji (solucja -> inaczej nasz projekt)  stąd skompilowana binarka znajduje się (w moim przypadku) w: <pulpit>\dexterxx.Tutorial\dexterxx.Tutorial\bin\Debug

Tak skompilowanej aplikacji nie powinieneś jeszcze nikomu przekazywać.  Przeanalizujmy jeszcze ścieżkę dla lepszej jasności:

Pierwsze dexterxx.Tutorial to dokładniej nazwa naszej solucji – solucja jest naszym (jakby to powiedzieć) globalnym projektem przy którym chcemy pracować. Może to być projekt naszych wielu mniejszych zadań i programów. Coś w stylu grupy, łączącej wszystko w jedną całość.

Drugie dexterxx.Tutorial to w rzeczywistości nasz faktyczny projekt (nie myl się zdublowaną nazwą solucji – projekt może nazywać się zupełnie inaczej), nad którym teraz pracujemy. Może to być pojedyncza aplikacja, może to być jakiś moduł do całej naszej solucji czy jakiś kontroler. W tym przypadku jest to nasza aplikacja, którą przed chwilą uruchamialiśmy.

bin to w skrócie „binaries” – binarki. Pliki wykonywalne inaczej. Wypluty przez kompilator nasz program.

Debug to typ zbudowanej naszej binarki. Jak pisałem wcześniej: tak skompilowanej aplikacji nie rozprzestrzeniamy wśród innych ludzi. Służy ona głównie do testowania i debbugowania naszego tworu. Wyróżniamy również folder Release – jest to już „wyczyszczona” i „finalna” wersja naszej skompilowanej aplikacji. W tych dwóch folderach również możemy znaleźć jakieś inne pliki po kompilacji, np. dodatkowe biblioteki. Należy je wtedy dołączyć do naszej aplikacji, jeśli nie występują standardowo w systemie (z tym tutorialem nie będzie tego problemu).

W folderach projektów również generują się inne foldery i pliki. Na tym poziomie nauki nie będziemy się nimi dzisiaj nawet interesować.

Kompilacja Release

Nie ma tu jakiejś większej różnicy. Wybieramy z panelu menu z select lista (gdzie pewnie teraz widzimy Debug) opcję Release. Przechodzimy do przebudowania naszej solucji Build, Rebuild Solution lub danego projektu: Build, Rebuild <nazwa projektu>.

Jeśli projekty wspólnie tworzą naszą aplikację – przebudowujemy całą solucję. Jeśli projektami są osobne aplikacje, zebrane w „jeden worek” – przebudowujemy dany projekt. Nie warto tracić czasu na zbędne kompilacje. Również każdy sposób kompilacji (Release czy Debug) możemy dostosować do swoich potrzeb w ustawieniach projektu – jedna kompilacja może korzystać z innych bibliotek niż druga…

Tak skompilowany projekt powinien być nieco lżejszy od wersji testowej. W moim przypadku ta zależność nie nastąpiła ;-) (projekt jest jeszcze zbyt ubogi na to).

Czym są cechy obiektów, pierwsze komponenty na formie

Programując w tego typu (wizualnych z komponentami) środowiskach niemożliwym jest wszystko robić strukturalnie (no chyba, że to o czym pisałem na początku). Wyświetlmy nasz Toolbox w którym znajdują się podstawowe komponenty do budowania okienkowych aplikacji. Jeśli jeszcze go nie znaleźliśmy, wybieramy View, Toolbox. Odnajdziemy coś takiego, jak u mnie po prawej stronie okna: (nie jestem pewien, czy najlżejsza wersja środowiska (Express) posiada ów kontrolki)

Są to podstawowe komponenty i kontrolki dostarczone wraz z naszą standardową biblioteką pod którą aktualnie pracujemy – w moim przypadku skład .NET Framework. W Borland było to VCL.

Kontrolki umieszczamy na formie przeciągając je, lub wybierając w toolboxie i klikając na naszej formie.

Czym są kontrolki? Czym są obiekty?

Teraz będzie trochę bardzo skrótowej teorii prostym językiem. Kontrolki mają swoje cechy: np. szerokość, tekst wewnątrz, kolor.. Kontrolki również reagują na różne akcje: kliknięcie, wciśnięcie przycisku klawiatury, samo pojawienie się (gdzieś) kontrolki. Kontrolką jest na przykład przycisk (masz go do wyboru w Toolboxie – cała reszta rzeczy tam również jest kontrolkami) – to obiekty (specjalnie przystosowane), którymi możesz łatwo zarządzać w środowisku w sposób wizualny i to one głównie budują wygląd naszej aplikacji okienkowej.

Nasza pusta forma również jest w pewnym sensie kontrolką. Też ma swoje cechy, też może wykonywać odpowiednie rzeczy w przypadku wystąpienia jakiegoś zdarzenia systemowego. Jakby nie patrzeć: dziedziczy ona z tej samej klasy, co inne kontrolki. Może tyle wystarczy, póki co.

Obiekty, klasy, struktury danych

To teraz porozmawiajmy o obiektach. Mam nadzieje, że znasz jakiś (również) obiektowy język programowania (Object-C, C++, PHP, Object-Pascal, Delphi, Java …)? Jeśli nie: sio poczytać czym są klasy, struktury, unie.. i czym jest dziedziczenie związane z klasami – to jest niezbędne minimum. No dobra, szybka piłka w rozumieniu obiektów:

Zaczynałeś pewnie naukę w jakimś języku programowania na jakichś podstawowych operacjach i zmiennych. Deklarowałeś lub i definiowałeś jakąś zmienną X, Y, Z. Robiłeś operację Z = X + Y; wyświetlałeś wynik na ekranie i było po krzyku. Nie było co prawda tutaj czego opisywać w sposób obiektowy, jeśli działaliśmy na podstawowych typach danych, na przykład liczbach.

A może spotkałeś się z pisaniem jakiejś podstawowej bazy danych? Na przykład baza danych zwierząt w schronisku. Psa dokładnie nie opiszesz w postaci prostego typu danych z pomocą jednej zmiennej. Możesz grzebać się strukturalnie, np. każdego psa reprezentuje 5 różnych zmiennych i piszesz jakbyś łopatą kopał:

Pies1_id = 4;
Pies1_imie = "Reksio";
Pies1_wiek = 5;
Pies1_klatka = 32;
Pies1_opiekun_id = 4;

Niby wygląda to ładnie, nie? Przychodzi drobne spowolnienie, jeśli miałbyś ów psa usunąć z rejestru. Bierzesz się za zerowanie każdej zmiennej z osobna?  Nie łatwiej byłoby zrobić usuń(Pies1)? Jasne że byłoby lepiej! Nie no co ty, jesteś ambitny, zamiast pisać każdą kolejną zmienną definiujesz swój format w którym opisujesz psa, np. przez jeden string w postaci:

Pies1 = "4:Reksio:5:32:4";

I jak tu łatwo zmienisz wiek psa? Zamienisz zawsze 10 znak ów stringu? No pomyśl. Albo lepiej, robisz z psa tablicę i każde pole reprezentuje jakąś inną daną.

Pies[] = {4, "Reksio", 5, 32, 4 };

Łups.. nie skompiluje się: pola tablicy muszą reprezentować ten sam typ danych. Więc co, na twardo liczby zamienisz na stringi? Z resztą kto się połapie w tym przeglądając Twój kod? A przy okazji zmarnujesz trochę pamięci..

No i tutaj na ratunek pojawiają się klasy, struktury no i unie  – skupimy się na klasach, reszta nie będzie tak potrzebna, ale i tak warto je znać.

Klasy, to inaczej wzorce obiektów – no i tutaj kończy się mój pomysł na definicję. W klasie możemy gromadzić informacje, na podstawie których utworzony obiekt będzie posiadał jakieś „wewnętrzne” dane w postaci zmiennych, dane mogą być publiczne (wszystko może się odwołać do tej zmiennej), prywatne (niedostępne z zewnątrz) jak i chronione (powiedzmy że.. przekazywane z pokolenia na pokolenie) – poudawajmy, że więcej możliwości nie ma. Klasy mogą zawierać również funkcje (publiczne, prywatne…), które mogą działać na obiekcie powołanym lub mogą być statyczne – wywoływane bez powoływania obiektu. No i klasy mogą dziedziczyć po sobie. To tak w skrócie.

Struktury: to samo co klasa, jednak domyślny dostęp do danych jest publiczny. Jest to starszy odpowiednik klasy znany z języka C. Klasy są następcą struktur.

Unie: „pudełka” wielkości największej wybranej przez nas zmiennej. Służy do trzymania różnych typów danych, tylko jednej zmiennej.

Opiszmy nieco nasz świat w postaci klasy:

  1. Powiedzmy, że istnieje klasa „obiekt_na_świecie”.
    1. Obiektem istniejącym na świecie może być kamień, człowiek, woda..
    2. Niech cechami klasy będzie: „położenie na świecie”, „typ obiektu”…
    3. Funkcje wewnętrzne: „zniszcz_obiekt”, „zmień_położenie”…
  2. Następnie wymyślmy np. „organizm_żywy” – dziedziczy on z „obiekt_na_świecie” (przejmuje jego cechy i funkcje).
    1. Można powołać obiekt: człowiek, krowa, bakteria, ale na przykład nie kamień ani woda na podstawie tej klasy.
    2. Powiedzmy, że w tej klasie mamy następujące cechy: „poziom życia”, „wiek”, „głód”…
    3. Funkcje wewnętrzne: „odpoczywaj”, „zjedz_cos”, „zmień_wiek”…

Możemy teraz utworzyć nasz obiekt na podstawie klasy „organizm_żywy”: człowiek.

Człowiek może zjeść coś, może w razie chęci odpoczywać. Może również zostać zniszczony lub może zmienić położenie, bo odziedziczył takie możliwości po „obiekcie_na_świecie” – oczywiście, o ile „prywatność” tych funkcji na to pozwala. Również można zmieniać jego wewnętrzne cechy i tak oto ręcznie można zmienić jego pozycję, lub poziom głodu – wszystko zależnie od tego, jak określiliśmy „dostęp” do tych cech i czy przypadkiem nie chcemy, aby funkcje za to odpowiadały.

To tak na szybko było: uczenie przez gnojenie ;-)

Kontrolki ciąg dalszy

Opiszmy teraz kontrolki o których była mowa wyżej w sposób bardziej obiektowy: wybierzmy na przykład zwykły przycisk: „Button” – przypadku .NET.

Możesz teraz przeciągnąć ów przycisk na formę aby bliżej mu się przyjrzeć. Możesz znowu uruchomić aplikację, aby w rzeczywistości pobawić się buttonem – w naszym środowisku klikając na niego automatycznie przejdziesz do funkcji, w której będziesz oprogramowywał go (zajmiemy się tym za chwilę, spokojnie).

Analiza i porównanie

Przyjrzyjmy się najpierw od strony wizualnej: na pewno nasz przycisk ma jakiś wygląd, ma swoją szerokość, wysokość, jest w nim zawarty jakiś tekst, ma swoje miejsce (kordynacje) na formie, również wyróżnia się jakimś kolorem.

Jakie towarzyszą mu akcje: można go kliknąć lewym przyciskiem, prawym, klawiszem klawiatury, można próbować go przeciągać, również coś się może dziać przy jego utworzeniu

Dorzućmy na formę na przykład CheckBox, porównajmy te dwa obiekty, co mają ze sobą wspólnego?

  • obydwa wyświetlają jakiś (definiowany przez nas) tekst – aha, na pewno jest coś wspólnego przy mechanizmie wyświetlania tekstu
  • obydwa można kliknąć myszką, potraktować klawiaturą
  • mają swoje określone rozmiary, położenie
  • i inne

Tym wyróżnia się dziedziczenie wśród obiektów – zupełnie jak przykład z człowiekiem wyżej i funkcjach „zniszczenia obiektu” lub jego pozycji we świecie. Wystarczy napisać raz np. klasę odpowiedzialną za trzymanie informacji na temat położenia obiektu, a kolejne obiekty (przycisk i checkbox) automatycznie przejmują od „rodziców” te cechy – bez potrzeby ponownego pisania lub kopiowania danej części.

Zabawa na cechach (właściwościach) obiektów

Skoro masz już te dwie kontrolki na formie – czas zabrać się za ich dostosowywanie wedle naszych potrzeb. Wyświetl okno Propeties, o ile jeszcze go nie widzisz: View, Propeties Windows lub F4. Zaznacz jeszcze wcześniej nasz przycisk. Powinieneś widzieć w okienku właściwości coś podobnego, jak na obrazku po prawej.

Przejrzyj większość cech tego przycisku, jeśli znasz nieco język angielski to i orientacyjnie dojdziesz co dana cecha opisuje. Oczywiście możesz je modyfikować, tak więc wprowadźmy takie podstawowe zmiany:

  • Z pewnością chcemy zmienić tekst wyświetlany w naszym przycisku:
    • Odszukujemy właściwość Text (lub Caption), wprowadzamy interesujące nas zmiany, przykładowo niech będzie wyświetlane „Kliknij mnie!”
    • Nie pomyl tej właściwości z Name – jest to nazwa obiektu, jakim posługujemy się by odwołać się do niego. Warto mimo wszystko przemianowywać obiekty, które umieszczamy na formie aby zachować porządek. Póki co pomińmy to.
  • Możemy zmienić nieco czcionkę naszego wewnętrznego tekstu. Coś na przykład bardziej cukierkowego i brzydkiego. Odnajdź cechę Font, kliknij na mały przycisk „doboru czcionki” przy tej właściwości. Wprowadź wedle swoich pomysłów zmiany.
  • Ostatecznie możemy jeszcze zmienić rozmiar i położenie naszego przycisku. Nie musimy tego robić ręcznie przez cechy: Width, Height, Location.Left…, a skorzystajmy z dobrodziejstw środowiska i wprowadźmy ruchem myszki odpowiednie zmiany.

Teraz weźmy pod lupę wcześniej wrzucony checkbox. Zaznaczmy go, zmieńmy tekst wyświetlany na „Zaznacz mnie!”. Dostosujmy go wedle własnych potrzeb.

Obydwie kontrolki są dość małe jak na naszą formę. Kliknijmy na formę, na jej rogach znajdziemy kwadraty umożliwiające zmianę wymiarów naszego okienka. Zmniejszmy je.

Może jak i zauważyłeś: można również zmieniać cechy naszej formy. Jasne, że możemy na początek zmienić paskudny napis „Form 1” w tytule okna. Przy okazji można zmienić właściwość FormBorderStyle na FixedSingle – co spowoduje uniemożliwienie zmiany rozmiaru naszego okna – oraz (o ile znajdziemy w przypadku innych środowisk) MaximizeBox na false – co wyłączy przycisk maksymalizacji na formie – nikt więc nie ruszy naszego małego okienka.

Reszta prób i błędów jako zadanie domowe ;-). Mój efekt końcowy wygląda póki co tak:

Uruchommy na test naszą aplikację, aby sprawdzić czy niczego nie zepsuliśmy. Oczywiście wszystko powinno działać: przycisk daje się wciskać bez efektów ubocznych, checkbox da się zaznaczyć. Zamykamy nasze dzieło.

Programowanie i praca na obiektach

Nie mamy się czym jeszcze pochwalić światu, nasza aplikacja nic nie robi. Na początek przyjrzyjmy się możliwościom „działania” dla każdej kontrolki. Wybierzmy błyskawicę / Events z okienka Propeties – zobaczymy coś w stylu obrazka po prawej.

Te akcje odpowiadają naszemu przyciskowi. Skupimy się na „prostszych” zdarzeniach:

  • Click – gdy nasz przycisk zostanie kliknięty (nie tylko przez myszkę)
    • Dla tego zdarzenia napiszemy odpowiednią akcję
  • Paint – zdarzenie wywoływane, kiedy przycisk musi zostać przerysowany na formie, na przykład w czasie zmieniania rozmiaru naszego okienka
  • DragDrop – gdy coś zostanie przeciągnięte na nasz przycisk (np. plik systemowy)
  • Enter – (focus), gdy „skupimy” się na tym przycisku (np. przez „skakanie” po kontrolkach przez tabulator)
  • MouseHover – gdy najedziemy myszką na przycisk
  • i inne

Kliknijmy dwa razy na nasz przycisk (lub dwa razy na akcję, którą chcemy oprogramować). Wygeneruje się podstawowy kod dla tego przycisku, od którego możemy rozpocząć pisanie naszej akcji dla niego.

Zanim weźmiemy się za pisanie. Rozejrzyjmy się wśród kodu, jaki nas otacza. Kod ten możemy również wyświetlić klikając prawym na naszą formę, następnie View Code. Akurat poruszamy się wśród naszej jednej przestrzeni nazw: Form1 – czyli naszej jedynej formy. Wszystko, co napiszemy w obrębie tej przestrzeni – jest osiągalne tylko tutaj, jeśli nie dołączymy ów pliku np. do innej formy, gdzie będziemy chcieli skorzystać z tej rzeczy – trochę skomplikowane, na razie porzućmy to.

Więc w przypadku C# po kliknięciu na nasz przycisk powinniśmy zobaczyć taki kod:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace dexterxx.Tutorial
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {

        }
    }
}

Gorzej jest niestety w przypadku C++. W takim przypadku zobaczymy:

//99 linijek, w tym: zmiana naszych właściwości, początkowe deklaracje, trochę burdelu jest...
#pragma endregion
	private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
			 }
	};
}

Jest różnica.

Przywitajmy się

Zacznijmy od czegoś podstawowego. Wróćmy kursorem na wcześniej przygotowane miejsce akcji dla przycisku. Przywitajmy się prostym okienkiem informacyjnym:

MessageBox.Show("Witaj świecie!");

Czyli ostateczna postać (pomijając początek):

namespace dexterxx.Tutorial
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Witaj świecie!");
        }
    }
}

Uruchommy czym prędzej naszą aplikację. Kliknijmy na przycisk, zobaczymy nasze okienko:

Ok, wciąż jeszcze za mało ambicji. Teraz poruszmy się między naszymi obiektami. Załóżmy, że chcemy w takim okienku informacyjnym wyświetlić wiadomość, czy nasz checkbox obok jest zaznaczony. Usuwamy naszą skromną linijkę napisaną przed chwilę. Teraz musimy dostać się do odpowiedniego obiektu jak i jego cechy, która posiada w sobie informacje, czy ów kwadracik jest zaznaczony. Klikamy na nasz checkbox, w górnej części okna Properties znajduje się jego nazwa, w tym przypadku: checkBox1.

Możemy w tym czasie już odnaleźć cechę w okienku Properties odpowiedzialną za interesujący nas stan – stan zaznaczenia. Jedziemy od góry.. to nie.. to nie.. może to? Zmieniamy na inną wartość… Nie.. dochodzimy w końcu do cechy checked, jak nie trudno z angielskiego przetłumaczyć. Zmieniamy na true – widzimy, kwadracik w naszym projekcie się zaznaczył. Bingo! O to nam chodziło. Wracamy do domyślnej wartości false lub zostawiamy tak jak jest. Jednak nie zawsze powinniśmy w ten sposób odnajdować interesującą nas cechę: nie wszystkie modyfikacje mogą wpływać na wygląd kontrolki w środowisku. Nie zawsze również to w czym pracujemy jest kontrolką – można po prostu grzebać w obiekcie nie widząc jakichkolwiek zmian, tutaj z pomocą przychodzi dokumentacja, orientacyjne nazwy cech i metod lub „żywe” próby na obiekcie.

Wracamy do naszej funkcji wykonywanej po kliknięciu przycisku, piszemy odpowiednie linijki, doprowadzamy do takiej postaci:

private void button1_Click(object sender, EventArgs e)
{
    //MessageBox.Show("Witaj świecie!");

    if (checkBox1.Checked)
        MessageBox.Show("Kwadracik jest zaznaczony!");
    else
        MessageBox.Show("Nie.. wciąż pusto");
}

Odwołujemy się do obiektu checkBox1 – został tak nazwany w naszej aplikacji – możemy to zmienić w oknie propeties, cecha name. Wewnątrz tego obiektu mamy jakieś jego wewnętrzne właściwości, nas aktualnie interesuje stan zaznaczenia (checked), odwołujemy się „wgłąb” obiektu (w przypadku c#) z pomocą kropki (.).

Kompilujemy, testujemy.

Pamiętać również musimy o zgodności typów. W tym przypadku jest całkiem łatwo: cecha checked checkboxa jest typu bool albo jak niektórzy wolą: Boolean. Przyjmuje zatem tylko dwie wartości: true lub false, odpowiednio 1 albo 0. Pamiętajmy, że wnętrze nawiasu operatora if potrzebuje testu kończącego wszystko w najprostszej postaci: true lub false. Nie potrzebujemy w tym przypadku pisać:

if (checkBox1.Checked == true)

gdyż jest to równoznaczne. Inaczej jest w przypadku bardziej złożonych typów.

Dobrodziejstwa środowiska

Autouzupełnianie kodu

Jest taka cudowna kombinacja klawiszy w większości środowisk programistycznych: CTRL + SPACJA. Odpowiada ona za okienko podpowiedzi w czasie wpisywania jakichś znaków: umożliwia szybsze odwołanie się do danego obiektu, metody, zapewnia podpowiedzi i pokazuje „możliwości” w obrębie danej przestrzeni nazw / klasy / obiektu itd.

Wpisując już początek klasy (mess) lub jej pierwsze duże litery (mb) dostaję podpowiedź, czy przypadkiem nie mam na myśli MessageBox – a mam na myśli, nieprawdaż? Mogę automatycznie umieścić tą podpowiedź z pomocą klawisza ENTER lub kropki, a następnie piszę dalej. Kropka jest niezwykle szybka i przyjemna programując w C#, nie to samo co odpowiednik C++: strzałka w głąb w postaci myślnika i operatora logicznego większe od (->) czy statycznie: podwójny dwukropek (::).

Korzystanie z tej funkcjonalności jest cholernym minimum i obcinam głowy za wklepywanie literka po literce.. i to jeszcze z błędami.

Podpowiedź argumentów

Kolejnym niezbędnikiem są podpowiedzi na temat argumentów, jakie przyjmuje dana funkcja. No więc wciśnijmy kropkę, jak zatrzymaliśmy się na autouzupełnianiu. Wybierzmy jedyną publiczną funkcję w tamtej przestrzeni i sprawdźmy co nam środowisko podpowie.

Może nie jest to w pełni czytelne, szczególnie przy takiej ilości przeładowań funkcji, ale jednak bardzo pomocne i teraz spróbujmy coś z tego wyczytać. Jak widzimy w środku tipa: funkcja może przyjąć przykładowo tylko jeden argument, nazywa się on text, jest typu string. Dokładnie w taki sposób wykorzystywaliśmy tą funkcję w przykładach wyżej – text to nasza informacja wyświetlana w okienku.

Jak widać jeszcze po dwukropku za nawiasem argumentów funkcji (niczym w Pascalu/Delphi) – zwraca ona rezultat typu DialogResult. No i teraz możemy poczuć się biednie… – nie wiemy jak ugryźć taki typ danych.. aż do czasu, gdy ujawnię fajną logikę na którą ktoś kiedyś wpadł.

Jeszcze zanim zajmiemy się zwracaną wartością (do której mam nadzieje sam dojdziesz): widzimy poniżej naszego wykorzystywanego wcześniej przykładu, że można dorzucić drugi argument string o nazwie caption, i jak mam nadzieje się domyślasz: chodzi tutaj o tytuł okienka informacyjnego. Możesz już teraz iść to sprawdzić i przetestować, jednak za chwilę rzucimy się z większym przykładem.

Teraz o wyżej wspomnianej logice

Rzucamy się na głęboką wodę. Wykorzystajmy przeładowanie 4 argumentowe tej funkcji. Wiesz już, co należy podać w pierwszych dwóch argumentach.. ale co z resztą? Szczególnie problemem jest buttons typu MessageBoxButtons i icon typu MessageBoxIcon. Cóż począć? Usuńmy najpierw napisaną linijkę, poszukajmy czegoś z pomocą autouzupełniania na temat dwóch nieznanych nam wyżej typów. Wpisujemy Messa… i już widzimy nasze obydwie możliwości: MessageBoxButtons i MessageBoxIcon. Dorzućmy kropkę za jednym z nich.. i co nam wypluwa hint? Czyżby to były nasze możliwości argumentów?

Owszem. Możemy posłużyć się tymi stałymi/zmiennymi przy ustalaniu argumentów naszego okna.

Wyprodukujmy teraz coś takiego: Po kliknięciu przycisku wyświetlamy zapytanie, czy kwadrat został wcześniej zaznaczony. W oknie wystąpią dwie możliwości: Tak lub Nie. Jeśli użytkownik zaznaczy Tak przy zaznaczonym kwadraciku, lub Nie przy niezaznaczonym – pogratulujemy mu. Krok po kroku wygląda to tak:

  1. Wyświetlamy okienko z pytaniem, użytkownik odpowiada
  2. Sprawdzamy, czy odpowiedź użytkownika pokrywa się z kwadracikiem
  3. Dokonujemy odpowiedniej akcji

To bierz się za pisanie, a za chwilę przejdź dalej z czytaniem, aby zweryfikować swój kod.

I jak? Pewnie znajdą się jakieś drobne poprawki, ale jeśli wykonałeś zadanie, to gratulacje ;-). Chociaż tego na co dzień się nie praktykuje, to tym razem dla łatwiejszego Twojego zrozumienia napisałem kod typowo w języku polskim. Staraj się jednak wszystko produkować w języku angielskim. Ja doszedłem do takiego kodu, mam nadzieje, że jest dla Ciebie w pełni zrozumiały.

private void button1_Click(object sender, EventArgs e)
{
    // Dla ładu utwórzmy kilka zmiennych zawierających nasze parametry
    string Tytul = "Pytanie milordzie";
    string Pytanie = "Powiedz mi drogi użytkowniku, czy kwadracik jest zaznaczony";
    MessageBoxIcon Ikonka = MessageBoxIcon.Question; // Wybieramy ikonkę do okienek pytających
    MessageBoxButtons Guziczki = MessageBoxButtons.YesNo; // i nasze możliwe opcje: [tak] lub [nie]

    // Nasze komunikaty
    string Poprawnie = "Brawo, odpowiedziałeś poprawnie!";
    string Niepoprawnie = "Niestety, błędna odpowiedź";

    // W tej zmiennej zapiszemy odpowiedź z okienka
    // Zauważ, że musi zmienna być takiego samego typu, jaki typ zwraca dana funkcja
    DialogResult Odpowiedz;     

    // Tutaj zadajemy pytanie i zapisujemy wynik
    Odpowiedz = MessageBox.Show(Pytanie, Tytul, Guziczki, Ikonka);

    // Teraz sprawdźmy co użytkownik zrobił
    switch (Odpowiedz)
    {
        // Użytkownik wybrał TAK
        case DialogResult.Yes:
            if (checkBox1.Checked)    // Powiedział, że jest zaznaczony - jeśli jest zaznaczony, gratulacje!
                MessageBox.Show(Poprawnie);
            else  // Przypadek, gdy użytkownik skłamał
                MessageBox.Show(Niepoprawnie);
            break;

        // Użytkownik wybrał NIE
        case DialogResult.No:
            if (!checkBox1.Checked)
                MessageBox.Show(Poprawnie);
            else
                MessageBox.Show(Niepoprawnie);
            break;

        // Inny, niemożliwy przypadek: magiczne kliknięcie zablokowanego krzyżyka?
        default:
            MessageBox.Show("Jak to zrobiłeś?");
            break;
    }

}

Sprawdź teraz czym nasz kod się różni, ewentualnie dokonaj poprawek, spróbuj wyciągnąć jakieś dobre strony z porównania. Jeszcze po czasie doszedłem do wniosku, że zamiast wywoływać od razu MessageBox.Show po sprawdzeniu – lepiej zachować informację o poprawności odpowiedzi do osobnej zmiennej, potem na jej podstawie wykonywać akcje „Gratulacji” lub przeciwnie.

Własna funkcja i zmienne globalne

Niegdyś gdy zaczynałem zabawę w środowiskach obiektowych, za cholerę nie potrafiłem dojść do tego, jak utworzyć jakąś zmienną globalną albo „ogólnodostępną” funkcję (czyli globalną). Jak pisałem wyżej: cokolwiek napiszesz w obrębie pliku i przestrzeni nazw danej formy – tam tylko będziesz mógł z tego korzystać.. no chyba, że jednak dostaniesz się do tej przestrzeni (w odpowiedni dla danego języka sposób) i zrobisz co zechcesz.. no cóż, skłamałem, że się nie da. Możesz pisać również w „ogólnej” przestrzeni, „ponad” swoją formę i równie łatwo się do tego odwoływać.

Dogrzebujemy się do naszego kodu. Szukamy początek naszej przestrzeni projektu: dexterxx.Tutorial. W języku C# od razu można dostrzec, że nieco niżej zaczyna się nasza publiczna klasa okienka, przy którym dłubiemy dzisiaj. Nie wejdziemy tam z buciorami póki co – załóżmy, że osadzamy w pliku tej formy jakąś funkcję, a potem nagle zechcemy wykorzystać ją w innym oknie (w innej przestrzeni nazw) – jednak nie wpadamy akurat na pomysł, by przenieść tą funkcję do osobnego, wspólnego pliku.

I tu nastąpił problem: designer wywalił się po zapisaniu i powrocie do widoku formy. Wszystko przez to, że wymaga on, aby pierwsza klasa w obrębie danego pliku dotyczyła naszej formy. Ok, przepraszam.. zabieramy naszą aktualnie pisaną klasę na koniec pliku…

Co kończy się na czymś takim:

[...]

        private void button1_Click(object sender, EventArgs e)
        {
            [...]
        }
    } // zamknięcie Form1

    // Nasza osobna klasa, niezwiązana z okienkami
    public class Helpers
    {
        public static void say_something()
        {
            MessageBox.Show("Jestem tu!");
        }
    }
} // zamknięcie namespace dexterxx.Tutorial

Jak widać: nasza funkcja nic nadzwyczajnego nie robi: w znany nam sposób wyświetla okienko z informacją.

Zauważ, że jest to funkcja statyczna: więc może zostać wywołana (jak z przestrzeni nazw) bez tworzenia obiektu ten klasy.

Teraz dodajmy do naszego projektu nową formę. Klikamy prawym przyciskiem na nasz projekt, Add, Windows Form. Zobaczymy naszą nową formę. Dodajmy od razu na nią przycisk, którym będziemy wywoływać funkcję z pliku Formy1. Wróćmy na widok Form1, również dodajmy przycisk, którym wyświetlimy naszą nową formę.

Podwójne kliknięcie na przycisk. Wrzucamy do niego następujący kod (w sumie to widoczny na screenie po prawej):

Form2 F2 = new Form2();
F2.Show();

Tworzymy nowy obiekt, który będzie naszą formą na podstawie „ustawionej” klasy Form2. Wywołujemy metodę wyświetlania tego obiektu.

Wracamy do Form2, oprogramowujemy nasz przycisk wrzucając taki kod:

dexterxx.Tutorial.Helpers.say_something();

Tadam. To wszystko! …niestety tylko w C#. Najpierw analiza, potem narzekanie: Odwołujemy się do przestrzeni nazwy naszego projektu dexterxx.Tutorial, stamtąd już bez problemu możemy zapodróżować do naszej klasy Helpers a potem już tylko wywołujemy interesującą nas publiczną funkcję – to tyle. „Wyższa siła” związana z C# dba za nas o to.

Czas na narzekanie: sprawa zupełnie strasznie wygląda w przypadku języka C++: trzeba samemu zadbać o dołączenie odpowiednich plików z zadbaniem, aby nie doszło do krzyżówki czy doszukanie się odpowiedniej przestrzeni nazw, aby móc skorzystać z funkcji chowanej gdzieś na zewnątrz, co gorsza, w pliku formy, z której została ów forma wyświetlona.. Tak więc do Form1.h include’ujemy na początku pliku Form2.h. Teraz już możemy utworzyć obiekt na podstawie klasy Form2, wywołać Show. Będąc w Form2 nie wywołamy tak prosto Helpers::say_something().. Musimy dołączyć „Form1.h”.. ale czy na pewno? Teraz trzeba zainteresować się pojęciem strażnika kompilacji, ale nie będę teraz o tym w głowie mącił… zapomnijmy teraz o tym problemie i porzućmy go.

Zmienne

Zmienne traktujemy całkowicie podobnie. Określamy do jakiej przestrzeni nazw mają należeć i w jakiej klasie, dbamy sami o ich osiągalność, definiujemy im wartości np. przez konstruktor (w przypadku tylko obiektowych języków). Mogą być również inne różnice między poszczególnymi językami: tak między w pełni obiektowym C# nie postawimy sobie legalnie i „strukturalnie” dowolnej zmiennej, jak to mogliśmy w C++ robić, niekoniecznie w obrębie obiektu/klasy.

Umiejętność rozumienia błędów

Nie można tego się w pełni nauczyć. Zdarzają się błędy w czasie kompilacji – to jest wiadome. Jeśli znamy dobrze podstawy danego języka – powinniśmy sobie zawsze poradzić ze zrozumieniem błędów wyświetlanych w Error List‚ie. Nie daję teraz głowy, że ów lista jest domyślnie widoczna w środowisku, tak więc możemy ją pokazać przez View, Error list lub CTRL + \ a następnie CTRL + E. Zawsze dostaniemy tam informację o ostrzeżeniach i błędach związanych z kompilacją.

Jeśli już nie rozumiemy czego błąd dotyczy – google, tam zawsze znajdziemy rozwiązanie, poprawimy naszą wiedzę.

Co teraz…?

Minęły dwa dni (a potem ponad tydzień) od napisania tego wszystkiego wyżej. Sam jakoś utraciłem pomysł co by tutaj dalej zapisać. Z pewnością polecam Ci jeszcze zapoznać się z tymi punktami:

  • Weź teraz pod lupę wszelkie „podstawowe” kontrolki zawarte w toolboxie, popatrz jak działają, jak się je kontroluje, co można z nimi zrobić i póki co na ich podstawie zbuduj jakąś aplikację
  • Zapoznaj się z MSDNAA i nauczy się czytać i rozumieć dokumentację: programowanie nie ogranicza się do przeciągania elementów na formę, trzeba wiedzieć czym w jaki sposób mamy działać
  • Powtórz to, co przeczytałeś tutaj
  • Napisz jakąś kolejną prostą aplikację. Tym razem spróbuj wykorzystać inne kontrolki i żeby to nie było znowu Hello World MessageBox’em
  • Pytaj, proś o drugi taki post w komentarzach z propozycjami, co powinienem poruszyć ;-)
  • I jeszcze raz zaproponuj jakiś pomysł w komentarzu, a wspólnie go spróbujemy zrealizować ;-)

Zakończenie

Dla niektórych „w końcu”, dla niektórych „już?!”. Pisząc tego posta popełniłem masę błędów i tak zdarzyło się klasy zrównywać do obiektów (a to obiekty są na PODSTAWIE jakiejś klasy) oraz wiele innych językowych błędów – nie można tego postu traktować jako dokumentacji albo chociażby dobrej książki o obiektowym programowaniu. Chociaż jest to drobny początek, niekoniecznie poprawny, jak to wszystko wygląda ;-)

Tak, karygodne błędy poprawię, wystarczy je teraz tylko wychwycić, a Tobie.. pomógł coś ten post?

Wkrótce planuję pokazać jak programować pod Windows Mobile również z platformą .NET.. więc krzycz jeśli jesteś ciekawy tego ;)

Tagi: , , , , , ,

Komentarze 2 komentarze

Brawo!
Dzięki za przystępny sposób omawiania trudnych spraw.
Nawet ja się troszke poduczę, choć nie będe napewno programistą, ale lepiej zrozumiem (pewnie po ponownym przeczytaniu) zasady.

Pozdrawiam bardzo serdecznie i czekam na dalsze lekcje:-)))

No ciekawie i prosto wytłumaczone podstawy tworzenia aplikacji okienkowych w Visual Studio :)

Borland oczywiście jest specyficzny i tam wygląda to trochę inaczej ale osoba która ma trochę olejku w głowie sobie poradzi :)

Pozdrawiam.

Skomentuj