P019 - Pierwszy program dla MS-Windows
Programy uruchomiono w środowisku Bloodshed Dev-C++ 4.9.9.2.
// I Liceum Ogólnokształcące
// im. K. Brodzińskiego
// w Tarnowie
//--------------------------
// Koło informatyczne 2006/7
//--------------------------
// Program: P019-01
//--------------------------

#include <windows.h>

main()
{
   MessageBox(0, "Witaj w świecie okienkowym\nI-LO w Tarnowie", "Pierwszy program dla MS-Windows", MB_OK);
   ExitProcess(0);
}
Efekt uruchomienia programu
obrazek

       Wyjaśnienie

Na kolejnych kilku zajęciach zajmiemy się podstawami programowania systemu MS-Windows. W systemie tym program musi współpracować ze środowiskiem graficznym. Dlatego programowanie MS-Windows wymaga dobrej znajomości sposobu pracy systemu oraz setek procedur, za pomocą których komunikujemy się z różnymi składnikami systemu.

W pierwszym programie wykorzystujemy dwie procedury systemowe, które wykonają za nas "czarną robotę". Ich deklaracje są umieszczone w pliku nagłówkowym windows.h, który bezwarunkowo musi być dołączony do aplikacji tworzonych dla środowiska MS-Windows.

Już w tak prostym programie występuje kilka pojęć, które musimy poznać, aby efektywnie programować okienka:

Proces

System MS-Windows jest wielozadaniowym systemem operacyjnym (ang. multitasking operating system). Oznacza to, iż naraz w pamięci komputera może być uruchomione wiele różnych programów. Jeśli komputer posiada jeden procesor, to oczywiście programy te nie wykonują się jednocześnie. System zarządzania wykonaniem zadań (ang. task scheduler) tworzy tzw. kolejkę procesów, na której umieszcza odwołania do uruchomionych programów lub ich procesów - jeden program może utworzyć kilka równoległych procesów, które nazywamy wątkami (ang. threads).

Programy w kolejce uruchamiane są naprzemiennie co pewien czas. Działają przez krótką chwilę, następnie są zamrażane i z kolejki uruchomiony zostaje kolejny program (proces). Dzięki temu użytkownikowi "wydaje" się, iż jednocześnie pracuje wiele programów.

Gdy proces kończy swoje działanie, musi o tym poinformować system. Do tego celu służy właśnie wywołanie procedury ExitProcess(). Parametrem jest kod wyjścia, czyli 32-bitowa liczba zwracana przez proces przy zakończeniu. Ponieważ procesy mogą być tworzone jako wątki w innych procesach, kod wyjścia służy często jako informacja, czy dany proces zakończył się poprawnie, czy też nie. U nas nie wykorzystujemy kodu wyjścia, zatem procedurę wywołujemy z parametrem 0:

  ExitProcess(0);

Uchwyt / dojście

Ponieważ w pamięci może znajdować się kilka programów, nie mogą nawzajem zakłócać się. W tym celu w systemie MS-Windows istnieje mechanizm ochrony pamięci przydzielanej procesom. Mechanizm ten odnosi się również do danych wewnętrznych systemu MS-Windows. Oznacza to, iż procesy nie mogą odwoływać się bezpośrednio do obszarów danych systemu lub innych procesów - muszą to robić za pomocą odpowiednich funkcji systemowych (i bardzo dobrze, inaczej jakiś źle lub złośliwie napisany program mógłby narobić wiele zamieszania).

Problem ten rozwiązano za pomocą tzw. uchwytów lub dojść (ang. handle). Są to liczby całkowite, które jednoznacznie identyfikują wewnętrzne struktury danych Windows. Możemy je traktować jak numery identyfikacyjne. Wiele funkcji systemowych będzie wymagało jako jednego z parametrów właśnie uchwytu np. okienka, kontrolki, urządzenia graficznego itp., na którym dana operacja ma zostać przeprowadzona. Elementy systemu identyfikowane przez uchwyty nazywamy zasobami (ang. resources).

Jeśli uchwyt ma wartość 0, to nie identyfikuje żadnego zasobu.

Funkcja MessageBox()

W systemie MS-Windows jest udostępniona dla programisty biblioteka procedur i funkcji, zwana WinAPI (Windows Application Programming Interface - interfejs programowy aplikacji Windows). W naszym programie wykorzystujemy z WinAPI funkcję MessageBox() (okienko wiadomości), która tworzy proste okienko wyświetlając w nim przekazane w parametrach teksty oraz przyciski. Deklaracja tej funkcji jest następująca:

int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);

 

HWND - typ danych dla uchwytów okienek (ang. Handle of WiNDow).
LPCTSTR - typ danych oznaczający adres (wskaźnik) tekstu zakończonego znakiem '\0' (ang. Long Pointer of ConstanT STRing)
UINT - typ danych oznaczający 32-bitową liczbę całkowitą bez znaku (ang. Unsigned INTeger).

 

hWnd - określa uchwyt okna procesu, z którego wywołana została funkcja MessageBox(). W naszym programie nie utworzyliśmy żadnego okna, dlatego jako uchwyt przekazaliśmy wartość 0.
lpText - wskaźnik tekstu, który zostanie umieszczony w oknie informacyjnym. Tekst może składać się z kilku wierszy zakończonych znakiem '\n'.
lpCaption - wskaźnik tekstu, który pojawi się na pasku tytułowym okienka informacyjnego
uType - określa rodzaj okna. Głównie chodzi tutaj o zestaw przycisków, które pojawią się w obszarze okna oraz o ikonkę umieszczaną po lewej stronie tekstu. Możliwych wartości jest dużo. My zastosowaliśmy stałą MB_OK, która powoduje wyświetlenie pod tekstem pojedynczego przycisku OK. Kliknięcie w ten przycisk kończy działanie funkcji MessageBox().

Typ okna informacyjnego możemy zestawiać z poniższych grup stałych za pomocą operatora C++ | (suma bitowa):


Grupa stałych określająca wyświetlane przyciski:

MB_ABORTRETRYIGNORE - przyciski Przerwij, Ponów próbę, Ignoruj

MB_HELP - oprócz przycisku OK w okienku pojawi się przycisk Pomoc. Kliknięcie w ten przycisk lub naciśnięcie klawisza F1 spowoduje wysłanie przez Windows do właściciela okna informacyjnego wiadomości WM_HELP. Obsługą takich wiadomości zajmujemy się w dalszych przykładach.

MB_OK - pojawi się jeden przycisk OK. Jest to wartość standardowa.

MB_OKCANCEL - pojawią się przyciski OK i Anuluj.

MB_RETRYCANCEL - pojawią się przyciski Ponów próbę i Anuluj

MB_YESNO - pojawią się przyciski Tak i Nie

MB_YESNOCANCEL - pojawią się przyciski Tak, Nie i Anuluj


Do powyższych wartości możemy dołączyć (operator |) stałe określające rodzaj wyświetlanej ikony w okienku informacyjnym:

MB_ICONEXCLAMATION - pojawi się ikona znaku z wykrzyknikiem.

MB_ICONINFORMATION - pojawi się dymek z małą literką i.

MB_ICONQUESTION - pojawi się dymek z pytajnikiem.

MB_ICONSTOP - system wygeneruje krótki dźwięk i pojawi się ikona stopu.


Kolejna grupa stałych określa, który z przycisków (jeśli jest ich więcej niż 1) okienka będzie przyciskiem standardowym - takim, który jest uaktywniany po naciśnięciu klawisza Enter.

MB_DEFBUTTON1 - pierwszy przycisk okna staje się przyciskiem standardowym. Jest to wartość domyślna.
MB_DEFBUTTON2 - standardowy będzie przycisk drugi.
MB_DEFBUTTON3 - trzeci.
MB_DEFBUTTON4 - czwarty.


Zwróć uwagę, iż funkcja MessageBox() zwraca wartość typu int. Wartość ta zależy od wybranego typu okna (parametr uType) i może być następująca:

IDABORT - wybrano przycisk Przerwij
IDCONTINUE, IDRETRY, IDTRYAGAIN - wybrano przycisk Ponów próbę
IDCANCEL - wybrano przycisk Anuluj
IDIGNORE - wybrano przycisk Ignoruj
IDNO - wybrano przycisk Nie
IDOK - wybrano przycisk  OK
IDYES - wybrano przycisk Tak.


Chociaż MessageBox() służy głównie do powiadamiania użytkownika o jakiś zdarzeniach powstałych w trakcie pracy programu (najczęściej są to komunikaty błędów), to poniżej mamy przykładową grę w odgadywanie liczb od 0 do 15 wykorzystującą tą funkcję. Zasady są bardzo proste - użytkownik wymyśla sobie liczbę od 0 do 15, a następnie odpowiada na pytania komputera. Komputer odgadnie liczbę po 4 pytaniach.

// I Liceum Ogólnokształcące
// im. K. Brodzińskiego
// w Tarnowie
//--------------------------
// Koło informatyczne 2006/7
//--------------------------
// Program: P019-02
//--------------------------

#include <windows.h>
#include <sstream>

using namespace std;

main()
{
  char tytul[] = "ZGADYWANKA LICZB   (C)2007 I-LO w Tarnowie";
  
  MessageBox(0, "Wymyśl liczbę z zakresu od 0 do 15 i kliknij OK", tytul, MB_OK | MB_ICONINFORMATION);
   
// generujemy tablicę masek bitowych

  int maski[] = {1,2,4,8};
   
  srand((unsigned)time(NULL));
  for(int i = 0; i < 50; i++)
  {
    int x = rand() % 4;
    int y = rand() % 4;
    int z = maski[x]; maski[x] = maski[y]; maski[y] = z;    
  }
  
// zadajemy 4 pytania, w każdym umieszczamy liczby zawierające
// bit z bieżącej maski. Odpowiedź TAK powoduje dodanie maski do
// zgadywanej liczby

  int liczba = 0;

  for(int i = 0; i < 4; i++)
  {
    ostringstream sout;

    sout << "Pytanie nr " << i + 1
         << "\n\nCzy twoja liczba jest jedną z liczb : \n\n";

    for(int j = 1; j < 16; j++) if(j & maski[i]) sout << j << " ";
    
    if(MessageBox(0, sout.str().c_str(), tytul, MB_YESNO | MB_ICONQUESTION) == IDYES)
      liczba += maski[i];
  }
  
// Wyświetlamy wynik

  ostringstream sout;

  sout << "Twoją liczbą jest liczba : " << liczba;
  MessageBox(0, sout.str().c_str(), tytul, MB_OK | MB_ICONINFORMATION);

// Koniec

  ExitProcess(0);
}

 

Efekt uruchomienia programu
obrazek

obrazek

obrazek


Objaśnienia

W przedstawionym powyżej programie występuje kilka fragmentów, które wymagają wyjaśnienia.
  int maski[] = {1,2,4,8};
   
  srand((unsigned)time(NULL));
  for(int i = 0; i < 50; i++)
  {
    int x = rand() % 4;
    int y = rand() % 4;
    int z = maski[x];
    maski[x] = maski[y];
    maski[y] = z;    
  }

Liczbę użytkownika odgadujemy testując, czy zawiera odpowiednie wagi binarne 1,2,4 i 8. Na przykład liczba 13 w zapisie binarnym postać:
13(10) = 1101(2).

Oznacza to, iż zawiera wagi 8, 4 i 1. Do testowania wag będziemy używali tablicy maski[]. Jednakże, aby zdezorientować użytkownika, zawartość tablicy pomieszamy losowo. Np. jeśli na początku ma ona zawartość: [1 2 4 8], to po wymieszaniu możemy otrzymać [4 1 2 8].

   int liczba = 0;

W tej zmiennej będziemy zbierać wagi wskazane przez użytkownika, które występują w jego liczbie.

   for(int i = 0; i < 4; i++)
  {
    ostringstream sout;

    sout << "Pytanie nr " << i + 1
         << "\n\nCzy twoja liczba jest jedną z liczb : \n\n";

    for(int j = 1; j < 16; j++)
      if(j & maski[i]) sout << j << " ";
    
    if(MessageBox(0, sout.str().c_str(), tytul,
        MB_YESNO | MB_ICONQUESTION) == IDYES) liczba += maski[i];
  }

Wykonujemy cztery obiegi pętli. W każdym obiegu zadajemy pytanie, czy liczba wymyślona przez użytkownika znajduje się wśród liczb zawierających wagę maski[i]. Jeśli odpowie twierdząco, wagę dodajemy do liczby.

Na uwagę zasługuje typ ostringstream. Jest to klasa strumienia znaków. Do zmiennej tego typu możemy zapisywać teksty zupełnie tak samo, jak do strumienia konsoli cout. Wprowadzona treść jest zapamiętywana wewnątrz strumienia. Na końcu wydobywamy zawartość strumienia funkcją składową sout.str(). Funkcja ta zwraca dane typu string, które w C++ są również klasą. Ponieważ funkcja MessageBox żąda adresu tablicy znakowej typu char, przekształcamy łańcuch na taki adres za pomocą funkcji składowej sout.str().c_str().

  ostringstream sout;

  sout << "Twoją liczbą jest liczba : " << liczba;
  MessageBox(0, sout.str().c_str(), tytul,
    MB_OK | MB_ICONINFORMATION);

Gdy pętla wykona 4 pełne obiegi, w zmiennej liczba otrzymamy wynik - liczbę wymyśloną przez użytkownika. Wykorzystując ponownie klasę ostrinstream zapisujemy w niej wynik i przekazujemy zawartość strumienia do MessageBox().

  ExitProcess(0);
}

 Kończymy działanie programu.


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2023 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe