Koło informatyczne – programowanie w środowisku MS-Windows

Proste programy dla Windows

Uruchom Code::Blocks, wybierz opcję Create a new project, a następnie w okienku dialogowym New from template wybierz Win32 GUI Project. Resztę zrób standardowo, jak dla aplikacji konsoli. Usuń z edytora program szablonowy i na jego miejsce wstaw poniższy kod:

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Prosty program dla Windows
// PRG_004
//-----------------------------------

#include <windows.h>

int main()
{
   MessageBox(0, "Witaj w świecie okienkowym\nI-LO w Tarnowie", "Pierwszy program dla MS-Windows", MB_OK | MB_ICONINFORMATION);
   ExitProcess(0);
}

 

Wybierz z menu opcję Project → Properties. W okienku dialogowym Project/target options wybierz zakładkę Build targets. W sekcji Selected build target options zmień typ na GUI application. Zatwierdź okienko dialogowe klikając na przycisk OK. Zmiana ta spowoduje, że nie będzie tworzone okno konsoli.

Skompiluj i uruchom program. Jeśli wszystko jest OK, to na ekranie pojawi się następujące okienko:

 

obrazek

 

W systemie Windows program musi współpracować ze środowiskiem graficznym. Dlatego programowanie MS-Windows wymaga dobrej znajomości sposobu pracy systemu oraz setek funkcji systemowych (tzw. API – Aplication Program Interface : Interfejs Programowy Aplikacji), 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 dobrze 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 funkcji 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 funkcję 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.

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 wymaga 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 funkcji o nazwie 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.

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Prosta gra dla Windows
// PRG_005
//-----------------------------------

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

using namespace std;

int main()
{
  char tytul[] = "ZGADYWANKA (C)2013 I-LO w Tarnowie";
  int i,j;

  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(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(i = 0; i < 4; i++)
  {
    ostringstream sout;

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

    for(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.

 

Prosty program okienkowy

Zastąp tekst w edytorze poniższym programem lub utwórz nowy projekt Win32 GUI i wstaw tekst programu do edytora.

 

// Koło Informatyczne I LO w Tarnowie
// (C)2013 mgr Jerzy Wałaszek
// Proste okienko w Windows
// PRG_006
//-----------------------------------

#include <windows.h>

// Tutaj umieszczamy nazwę klasy, wg której powstanie nasze okno
// Jest to zmienna globalna.
//--------------------------------------------------------------

char ClassName[ ] = "SimpleWinClass";

// Procedura obsługująca wiadomości dla okna, wywoływana przez
// MS-Windows przy okazji różnych zdarzeń, które należy obsłużyć
//--------------------------------------------------------------

LRESULT CALLBACK WndProc(HWND h, UINT uMsg , WPARAM wP, LPARAM lP)
{
  if(uMsg == WM_DESTROY)           // Jeśli wiadomość dotyczy zamknięcia okna,
  {
    PostQuitMessage(0); return 0;  // to je zamykamy i kończymy funkcję
  }
  else return DefWindowProc(h,uMsg,wP,lP); // Inaczej każemy systemowi obsłużyć tę wiadomość
}

// W Windows główna funkcja, od której uruchamiany jest program,
// nazywa się WinMain zamiast main.
//--------------------------------------------------------------

int WINAPI WinMain (HINSTANCE hinst, HINSTANCE hprevinst, LPSTR cmdLine, int cmdShow)
{
  WNDCLASSEX wc;  // struktura klasy okna
  MSG        ms;  // przechowuje wiadomości aplikacji
  HWND       h;   // Uchwyt naszego okna


  // Ustawiamy kolejne pola struktury klasy okna

  wc.cbSize        = sizeof(WNDCLASSEX);
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = WndProc;
  wc.cbClsExtra    = wc.cbWndExtra = 0;
  wc.hInstance     = hinst;
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  wc.lpszMenuName  = NULL;
  wc.lpszClassName = ClassName;
  wc.hIcon         = wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);

  // Rejestrujemy klasę okna w Windows. Jeśli się to nie uda, kończymy program

  if(!RegisterClassEx(&wc)) return 0;

  // Gdy klasa jest zarejestrowana, tworzymy na jej podstawie okno

  h = CreateWindowEx(0,                      // dodatkowe style okna, 0 oznacza ich brak
                     ClassName,              // adres tablicy char z nazwą klasy
                     "Nasze pierwsze okno",  // adres łańcucha z napisem tytułowym okna
                     WS_OVERLAPPEDWINDOW,    // styl okna
                     CW_USEDEFAULT,          // współrzędna X lewego górnego narożnika okna
                     CW_USEDEFAULT,          // współrzędna Y lewego górnego narożnika okna
                     300,                    // szerokość okna w pikselach, również może być CW_USEDEFAULT
                     200,                    // wysokość okna w pikselach, również może być CW_USEDEFAULT
                     0,                      // uchwyt nadrzędnego okna, jeśli istnieje
                     0,                      // uchwyt menu, jeśli istnieje
                     hinst,                  // uchwyt programu, który korzysta z okna
                     0                       // adres dodatkowych danych
                     );

  // Utworzone okno wyświetlamy na ekranie

  ShowWindow(h, cmdShow);

  // Uaktualniamy treść okna

  UpdateWindow(h);

  // Rozpoczynamy pętlę obsługi wiadomości napływających do naszego okna.

  while(GetMessage(&ms, NULL, 0, 0))  // Czekamy na wiadomość
  {
    TranslateMessage(&ms);            // Przekształcamy wiadomość
    DispatchMessage(&ms);             // Wysyłamy ją do procedury okna
  }

  // zwracamy wartość umieszczoną w polu wParam ostatniej wiadomości

  return ms.wParam;
}

 

Po skompilowaniu i uruchomieniu ukaże się okienko aplikacji:

 

obrazek

 

Powyższy program wyjaśnia powód nie rozpoczynania nauki programowania od systemu Windows. Jednakże warto dokładnie przeczytać podane tutaj informacje, ponieważ opisane funkcje i struktury występują praktycznie w każdym programie okienkowym. Z drugiej strony nie musimy się wszystkiego dokładnie uczyć na pamięć. Wystarczy, iż wiemy ogólnie, gdzie znaleźć odpowiednie wyjaśnienia i do czego służą opisane funkcje i struktury danych.

Zadaniem naszego programu jest utworzenie jednego prostego, pustego okna aplikacji, które będzie posiadało typowe zachowania okienkowe (na przykład możliwość przesuwania po ekranie, skalowania, sprowadzania na pasek zadań, itp.). W tym celu musimy wykonać następujące kroki:

  1. Zainicjować pola specjalnej struktury danych, zwanej strukturą klasy okna.
  2. Wykorzystać strukturę klasy okna do rejestracji tej klasy w systemie MS-Windows. Gdy klasa zostanie zarejestrowana, struktura klasy okna nie będzie już potrzebna i można ją wykorzystać do rejestracji innej klasy okna. Klasa okna definiuje parametry okienka – jego wygląd, kolory, menu itp.
  3. Po rejestracji należy utworzyć okno na podstawie klasy.
  4. Utworzone okno należy wyświetlić na ekranie i odświeżyć.
  5. Następnie w pętli należy sprawdzać wiadomości nadchodzące od Windows i wysyłać je do specjalnej procedury, zwanej procedurą okna, w której zostaną obsłużone. Pętla jest wykonywana dotąd, aż skończą się wiadomości dla naszego programu – wszystkie utworzone przezeń okna zostaną zamknięte. Wtedy kończymy funkcję WinMain.

Struktura klasy okna

Do rejestracji klasy okna wykorzystywana jest struktura typu WNDCLASSEX, która posiada następującą deklarację:

typedef struct
{
    UINT      cbSize;
    UINT      style;
    WNDPROC   lpfnWndProc;
    int       cbClsExtra;
    int       cbWndExtra;
    HINSTANCE hInstance;
    HICON     hIcon;
    HCURSOR   hCursor;
    HBRUSH    hbrBackground;
    LPCTSTR   lpszMenuName;
    LPCTSTR   lpszClassName;
    HICON     hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;

W definicji pól tej struktury występuje wiele nowych typów danych, zdefiniowanych specjalnie na potrzeby systemu MS-Windows.

UINT     32-bitowa liczba całkowita bez znaku, odpowiednik C++ to unsigned int.
WNDPROC   adres procedury okna, czyli procedury obsługującej napływające dla niego wiadomości.
HINSTANCE   uchwyt programu
HICON   uchwyt ikony
HCURSOR   uchwyt kursora
HBRUSH   uchwyt pędzla.
LPCTSTR   długi wskaźnik tablicy znaków zakończonych kodem 0

Poszczególne pola struktury WNDCLASSEX posiadają następujące przeznaczenia:

cbSize    określa rozmiar w bajtach struktury WNDCLASSEX. Rozmiar ten otrzymamy z sizeof(WNDCLASSEX).
style    określa style okna, które mogą być dowolną kombinacją (za pomocą operatora | ) stałych:
CS_BYTEALIGNCLIENT   

Wyrównuje obszar roboczy okna do granicy bajtu w kierunku x. Styl ten wpływa na szerokość okna oraz jego położenie poziome na ekranie monitora.

CS_BYTEALIGNWINDOW   

Wyrównuje okno do granicy bajtu w kierunku x. Styl ten wpływa na szerokość okna oraz jego położenie poziome na ekranie monitora.

CS_CLASSDC   

Przydziela jeden wspólny kontekst graficzny dla wszystkich okien w tej klasie. Ponieważ klasy okien są zależne od procesów, to możliwe jest utworzenie okien tej samej klasy przez wiele wątków danej aplikacji. Możliwe również jest jednoczesna próba użycia w wątkach tego samego kontekstu graficznego. Gdy tak się stanie, system zezwoli tylko jednemu wątkowi na ukończenie jego operacji graficznych.

CS_DBLCLKS   

Wysyła wiadomość o podwójnym kliknięciu myszką do procedury okna, gdy kursor myszki znajduje się w obszarze okna zbudowanego na podstawie tej klasy.

CS_DROPSHADOW   

W Windows XP: umożliwia rzucanie cienia przez okno. Efekt ten jest stosowany zwykle przez małe okna, które na krótko pojawiają się na ekranie  np. okna podpowiedzi lub menu, aby stworzyć wrażenie trójwymiarowości.

CS_GLOBALCLASS   

Określa, iż dana klasa okien jest globalną klasą aplikacji.

CS_HREDRAW   

Przerysowuje całe okno przy zmianie szerokości obszaru roboczego użytkownika.

CS_NOCLOSE   

Wyłącza przycisk zamykania okna na pasku tytułowym.

CS_OWNDC   

Przydziela oddzielny kontekst graficzny dla każdego okna w tej klasie.

CS_PARENTDC   

Ustawia prostokąt obcinania okna potomnego do prostokąta obcinania okna nadrzędnego. Dzięki temu okno potomne może rysować po swoim oknie nadrzędnym.

CS_SAVEBITS   

Zachowuje w postaci bitmapy część obrazu ekranu zakrytego przez okno tej klasy. Gdy okno zostanie usunięte, system wykorzysta zapamiętany obraz do przywrócenia ekranu.

Styl ten jest użyteczny dla małych okienek (menu, okna dialogowe, itp.), które są wyświetlane przez krótki okres czasu, a następnie usuwane. Styl ten zwiększa czas wymagany na wyświetlenie okna, ponieważ system musi najpierw przydzielić pamięć na obrazek.

CS_VREDRAW   

Przerysowuje całe okno przy zmianie wysokości obszaru roboczego.

lpfnWndProc   

adres procedury okna, która przetwarza i obsługuje wiadomości systemowe napływające dla okna utworzonego na podstawie tej klasy.

cbClsExtra   

określa liczbę bajtów, którą należy dodatkowo zarezerwować za strukturą klasy okna. System inicjuje te bajty na zero.

cbWndExtra   

określa liczbę dodatkowych bajtów, które należy zarezerwować za egzemplarzem okna. System inicjuje te bajty na zero. 

hInstance   

uchwyt programu zawierającego procedurę okna dla tej klasy.

hIcon   

uchwyt ikony klasy. Musi to być uchwyt do zasobu ikon. Jeśli będzie zawierać NULL, to system udostępni standardową ikonę.

hCursor   

uchwyt kursora klasy. Musi to być uchwyt do zasobu kursora. Jeśli będzie zawierać NULL, to aplikacja musi osobiście ustawić kształt kursora w momencie, gdy kursor myszki pojawia się ponad okienkiem aplikacji.

hbrBackground   

uchwyt pędzla tła klasy. Pole to może być uchwytem do fizycznego pędzla stosowanego do malowania tła lub może być wartością koloru. Stałe koloru wymienione są poniżej. Jeśli stosujemy stałą koloru, to należy dokonać rzutowania typu (HBRUSH):
COLOR_ACTIVEBORDER   kolor aktywnej ramki okna
COLOR_ACTIVECAPTION   kolor aktywnego tytułu okna
COLOR_APPWORKSPACE   kolor obszaru roboczego aplikacji
COLOR_BACKGROUND   kolor tła
COLOR_BTNFACE   kolor przycisków
COLOR_BTNSHADOW   kolor cienia przycisków
COLOR_BTNTEXT   kolor tekstu przycisków
COLOR_CAPTIONTEXT   kolor tekstu tytułu okna
COLOR_GRAYTEXT   kolor zablokowanego tekstu
COLOR_HIGHLIGHT   kolor wyróżnienia
COLOR_HIGHLIGHTTEXT   kolor wyróżnionego tekstu
COLOR_INACTIVEBORDER   kolor nieaktywnej ramki
COLOR_INACTIVECAPTION   kolor nieaktywnego tytułu
COLOR_MENU   kolor menu
COLOR_MENUTEXT   kolor tekstu menu
COLOR_SCROLLBAR   kolor paska przewijania
COLOR_WINDOW   kolor okna
COLOR_WINDOWFRAME   kolor ramki okna

Jeśli pole to zawiera wartość NULL, to aplikacja musi samodzielnie zamalowywać tło okna przy każdym żądaniu malowania obszaru roboczego.

lpszMenuName   

wskaźnik nazwy menu klasy. Jeśli pole zawiera NULL, to okna zbudowane na podstawie tej klasy nie będą posiadały żadnego menu.

lpszClassName   

wskaźnik nazwy klasy.

hIconSm   

wskaźnik małej ikony związanej z klasą okna. Jeśli pole zawiera NULL, system przeszuka zasoby ikon określone przez pole hIcon i tam dobierze odpowiednią ikonę.

Rejestracja nowej klasy okna

Gdy struktura WNDCLASSEX zostanie wypełniona odpowiednimi danymi, używamy jej do rejestracji klasy za pomocą funkcji:

ATOM RegisterClassEx
(
    CONST WNDCLASSEX *lpwcx
);
lpwcx    wskaźnik powyżej opisanej struktury WNDCLASSEX.

Jeśli wywołanie funkcji się powiedzie, to zwracana jest specjalna wartość identyfikująca jednoznacznie rejestrowaną klasę. W przeciwnym razie zwracane jest 0, co właśnie sprawdza nasz program.

Tworzenie okna

Gdy klasa okna zostanie zarejestrowana w systemie Windows, można na jej podstawie tworzyć okna robocze aplikacji. Do tego celu wykorzystujemy funkcję:

HWND CreateWindowEx
(      
    DWORD     dwExStyle,
    LPCTSTR   lpClassName,
    LPCTSTR   lpWindowName,
    DWORD     dwStyle,
    int       x,
    int       y,
    int       nWidth,
    int       nHeight,
    HWND      hWndParent,
    HMENU     hMenu,
    HINSTANCE hInstance,
    LPVOID    lpParam
);
dwExStyle    określa rozszerzone style okna dla tworzonego okna. Ponieważ w tym programie nie wykorzystujemy tego parametru, a jest on dosyć rozbudowany, opiszemy go, gdy stanie się potrzebny.
lpClassName    wskaźnik tablicy znaków zakończonych kodem 0, zawierających nazwę klasy.
lpWindowName    wskaźnik tekstu będącego tytułem okna.
dwStyle    określa styl tworzonego okna. Parametr może być dowolną kombinacją następujących styli:
WS_BORDER    tworzy okno z cienką linią ramki
WS_CAPTION    tworzy okno z paskiem tytułowym
WS_CHILD    tworzy okno podrzędne. Okno zawierające ten styl nie może posiadać paska menu. Również ten styl nie może występować razem ze stylem WS_POPUP.
WS_CHILDWINDOW    to samo co WS_CHILD.
WS_CLIPCHILDREN    z operacji graficznych okna będą wyłączone obszary przykryte przez okna potomne.
WS_CLIPSIBLINGS    powoduje wyłączenie z operacji graficznych dla okien potomnych obszarów zakrytych przez inne okna potomne. Jeśli ten styl nie jest ustawiony, to okna potomne mogą rysować po przykrywającym je obszarze innego okna potomnego.
WS_DISABLED    tworzy okno, które jest początkowo zablokowane. Zablokowane okno nie może odczytywać danych od użytkownika.
WS_DLGFRAME    tworzy okno posiadające krawędź w stylu typowym dla okienek dialogowych. 
WS_GROUP    określa pierwszą kontrolkę w grupie kontrolek.
WS_HSCROLL    tworzy okno z poziomym paskiem przewijania.
WS_ICONIC    tworzy okno, które początkowo jest zminimalizowane do ikony. To samo co WS_MINIMIZE.
WS_MAXIMIZE    tworzy okno pierwotnie zmaksymalizowane
WS_MAXIMIZEBOX    tworzy okno z przyciskiem maksymalizacji.
WS_MINIMIZE    tworzy okno, które początkowo jest zminimalizowane do ikony. To samo co WS_ICONIC.
WS_MINIMIZEBOX    tworzy okno z przyciskiem minimalizacji.
WS_OVERLAPPED    tworzy okno, które może być przykrywane innymi oknami. Okno takie posiada pasek tytułowy oraz brzeg.
WS_OVERLAPPEDWINDOW    tworzy okno zakrywalne z ustawionymi stylami WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, oraz WS_MAXIMIZEBOX.
WS_POPUP    tworzy wyskakujące okno.
WS_POPUPWINDOW    tworzy okno wyskakujące z ustawionymi stylami WS_BORDER, WS_POPUP i WS_SYSMENU.
WS_SIZEBOX    tworzy okno z brzegiem zawierającym ikonę skalowania.
WS_SYSMENU    tworzy okno z przyciskiem menu systemowego na pasku tytułowym. Styl WS_CAPTION również musi być dołączony.
WS_TABSTOP    Określa kontrolkę otrzymującą skupienie przy naciśnięciu klawisza TAB, Naciskanie klawisza TAB zmienia skupienie klawiatury na następną kontrolkę z ustawionym stylem WS_TABSTOP.
WS_THICKFRAME    tworzy okienko ze skalowalnym brzegiem  to samo co WS_SIZEBOX.
WS_TILED    to samo co WS_OVERLAPPED
WS_TILEDWINDOW    to samo co WS_OVERLAPPEDWINDOW
WS_VISIBLE    tworzy okno pierwotnie widoczne. Styl ten można włączać i wyłączać wykorzystując funkcje API ShowWindow lub SetWindowPos.
WS_VSCROLL    tworzy okno z pionowym paskiem przewijania.
x,y    określają początkową pozycję okna na ekranie. Współrzędne x i y odnoszą się do lewego górnego narożnika okna. W obu przypadkach można zastosować stałą CW_USEDEFAULT.
nWidth, nHeight    szerokość i wysokość okna.
hWndParent    jeśli tworzone okno jest własnością innego okna, to tutaj umieszczamy uchwyt właściciela
hMenu    uchwyt menu okna. Jeśli okno nie posiada menu, wpisujemy tutaj NULL.
hInstance    uchwyt programu, z którym jest skojarzone to okno
lpParam    wskaźnik wartości do przekazania do okna poprzez pole lpCreateParams struktury CREATESTRUCT, która jest wskazywana przez parametr lParam wiadomości WM_CREATE. Wiadomość jest wysyłana do tworzonego okna przez tą funkcję zanim zakończy ona swoje działanie.

Funkcja zwraca uchwyt do utworzonego okna. Uchwyt ten należy zapamiętać w osobnej zmiennej, ponieważ korzystają z niego funkcje obsługujące okno.

Pokazywanie okna na ekranie

Po utworzeniu okna pokazujemy je na ekranie za pomocą funkcji:

BOOL ShowWindow
(      
    HWND hWnd,
    int  nCmdShow
);
hWnd    uchwyt okna
nCmdShow    określa sposób wyświetlenia okna. Przy pierwszym wywołaniu okna aplikacji parametr ten powinien przyjąć wartość otrzymaną przez funkcję WinMain w parametrze  cmdShow. W kolejnych wywołaniach można stosować poniższe stałe:
SW_FORCEMINIMIZE    minimalizuje okno. Tą opcję zwykle stosujemy przy minimalizowaniu okna z innego wątku.
SW_HIDE    ukrywa to okno i aktywizuje kolejne okienko na pulpicie
SW_MAXIMIZE    maksymalizuje okno
SW_MINIMIZE    minimalizuje okno i aktywuje następne okno ze sterty ekranu
SW_RESTORE    aktywuje i wyświetla okno.
SW_SHOW    aktywuje okno i wyświetla je w bieżących rozmiarach i na bieżącej pozycji.
SW_SHOWDEFAULT    ustawia stan pokazywania okna na podstawie wartości SW_... określonej w strukturze STARTUPINFO, przekazanej do funkcji CreateProcess przez program, który uruchomił tą aplikację.
SW_SHOWMAXIMIZED    aktywuje okno i wyświetla je jako zmaksymalizowane
SW_SHOWMINIMIZED    aktywuje okno i wyświetla je jako zminimalizowane
SW_SHOWMINNOACTIVE    wyświetla okno jako zminimalizowane. Różni się od SW_SHOWMINIMIZED tym, iż okno nie jest aktywowane
SW_SHOWNA    wyświetla okno w bieżących rozmiarach i na bieżącej pozycji. Nie aktywuje okna.
SW_SHOWNOACTIVATE    wyświetla okno w jego ostatnich rozmiarach i na jego ostatniej pozycji. Nie aktywuje okna.
SW_SHOWNORMAL    aktywuje i wyświetla okno. Tą stałą aplikacja powinna wybrać przy pierwszym wyświetleniu okna.

Uaktualnienie obszaru roboczego okna

Wyświetlone na ekranie okno zwykle uaktualniamy za pomocą funkcji:

BOOL UpdateWindow
(
    HWND hWnd
);

Funkcja powoduje odświeżenie obszaru roboczego (client area) określonego okna przez wysłanie do niego wiadomości WM_PAINT. Wiadomość jest obsługiwana w procedurze okna.

Pobieranie wiadomości z kolejki

Gdy w systemie Windows pojawi się zdarzenie związane z naszym oknem, zostaje do niego wysłana odpowiednia wiadomość. System magazynuje wiadomości dla różnych okien na specjalnej kolejce. Do pobierania wiadomości wykorzystujemy funkcję:

BOOL GetMessage
(      
    LPMSG lpMsg,
    HWND  hWnd,
    UINT  wMsgFilterMin,
    UINT  wMsgFilterMax
);
lpMsg    wskaźnik struktury MSG, w której zostanie umieszczona wiadomość z kolejki wiadomości danego wątku. Struktura MSG jest następująca:
typedef struct
{
    HWND   hwnd;
    UINT   message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD  time;
    POINT  pt;
} MSG, *PMSG;
hwnd    uchwyt okna, którego procedura otrzyma wiadomość. Jeśli wiadomość odnosi się do wątku, to pole zawiera NULL.
message    określa identyfikator wiadomości.
wParam    dodatkowa informacja związana z wiadomością  pole 16 bitowe
lParam    dodatkowa informacja związana z wiadomością  pole 32 bitowe
time    określa czas wysłania wiadomości
pt    określa pozycję kursora we współrzędnych ekranu w czasie wysłania wiadomości.
hWnd    uchwyt okna, dla którego wiadomości należy pobrać. Okno musi należeć do bieżącego wątku. Jeśli hWnd zawiera NULL, to funkcja GetMessage pobierze wiadomości dla wszystkich okien należących do bieżącego wątku.
wMsgFilterMin    określa najniższą wartość wiadomości do pobrania.
wMsgFilterMax    określa największą wartość wiadomości do pobrania. Jeśli oba parametry mają wartość 0, to GetMessage pobiera wszystkie wiadomości. Parametry te stosuje się zwykle do filtrowania wiadomości pochodzących od klawiatury i myszy.

Funkcja zwraca wartość 0 po odebraniu wiadomości WM_QUIT. W pozostałych przypadkach wartość jest niezerowa.

Przekształcanie wiadomości klawiatury

Naciśnięcie lub zwolnienie dowolnego klawisza powoduje wysłanie wiadomości o tzw. klawiszach wirtualnych. Wiadomości te umożliwiają przetwarzanie dosłownie wszystkich klawiszy. Jeśli w naszym oknie interesują nas jedynie klawisze znakowe (czyli takie, które produkują litery, cyfry i znaki), to możemy przetłumaczyć wiadomości klawiszy wirtualnych na klawisze znakowe za pomocą funkcji:

BOOL TranslateMessage(const MSG *lpMsg);

Rozsyłanie wiadomości do okna

Pobraną z kolejki wiadomość rozsyłamy do odpowiedniego okna za pomocą funkcji:

LRESULT DispatchMessage(const MSG *lpmsg);

Wiadomość trafia do procedury okna. Wartość zwrotna jest wartością zwróconą przez procedurę okna. Najczęściej się ją ignoruje.

Analiza programu

char ClassName[ ] = "SimpleWinClass";
Nazwa klasy okna, wg której Windows będzie budowało okna naszej aplikacji.
LRESULT CALLBACK WndProc
  (
  HWND h,    // uchwyt okna
  UINT uMsg, // kod wiadomości
  WPARAM wP, // parametr 16-bitowy
  LPARAM lP  // parametr 32-bitowy
  )
{
  if(uMsg == WM_DESTROY)
  {
    PostQuitMessage(0); return 0;
  }
  else return DefWindowProc(h,uMsg,wP,lP);
}
Do obsługi zdarzeń musimy przygotować specjalną funkcję zwaną procedurą okna.

Na podstawie kodu wiadomości procedura okna wykonuje odpowiednie działanie  np. wywołuje inne funkcje w programie użytkownika.

W przypadku naszej procedury okna obsługiwana jest tylko jedna wiadomość:
WM_DESTROY  zamknięcie okna.

Po otrzymaniu tej wiadomości wywoływana jest funkcja PostQuitMessage() informująca system, iż wątek chce zakończyć swoje działanie. Następnie kończymy działanie zwracając 0.

Dla wszystkich pozostałych wiadomości, których nie obsługuje nasza procedura okna, wywołujemy funkcję DefWindowProc przekazując jej wszystkie otrzymane parametry. Funkcja ta dokona standardowej obsługi wiadomości (tzn. większość z nich po prostu zignoruje).

  int WINAPI WinMain
    (
    HINSTANCE hinst,     // uchwyt aplikacji
    HINSTANCE hprevinst, // obecnie prawie zawsze NULL
    LPSTR     cmdLine,   // wskaźnik wiersza poleceń
                         // uruchamiającego ten program
    int       cmdShow)   // określa początkowy sposób
                         // wyświetlania okna
{
Funkcja WinMain jest punktem startowym aplikacji okienkowej.
  WNDCLASSEX wc;  // struktura klasy okna
  MSG        ms;  // przechowuje wiadomości aplikacji
  HWND       h;   // Uchwyt naszego okna
Tutaj definiujemy trzy zmienne używane w naszej aplikacji.
  wc.cbSize        = sizeof(WNDCLASSEX);
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = WndProc;
  wc.cbClsExtra    = wc.cbWndExtra = 0;
  wc.hInstance     = hinst;
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  wc.lpszMenuName  = NULL;
  wc.lpszClassName = ClassName;
  wc.hIcon         = wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);

Pierwszą rzeczą w naszej aplikacji jest ustawienie pól struktury WNDCLASSEX, która posłuży do zarejestrowania w systemie Windows nowej klasy okienek  naszej klasy.

    if(!RegisterClassEx(&wc)) return 0;
Rejestrujemy klasę okna wykorzystując wypełnioną strukturę WNDCLASSEX. Jeśli rejestracja się nie uda, to natychmiast kończymy program.
    h = CreateWindowEx(0, ClassName, "Nasze pierwsze okno",
          WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
          300, 200, 0, 0, hinst, 0);
Na podstawie zarejestrowanej klasy tworzymy okno. Zwróć uwagę, iż do funkcji CreateWindowEx przekazuje się nazwę klasy oraz różne parametry dla tworzonego okna. Funkcja zwraca uchwyt utworzonego okna, który możemy przekazywać różnym funkcjom operującym na okienku.
    ShowWindow(h, cmdShow);
Gdy okno jest utworzone, możemy je umieścić na ekranie.
    UpdateWindow(h);
Następnie odświeżamy jego zawartość.
    while(GetMessage(&ms, NULL, 0, 0))
    {
        TranslateMessage(&ms);
        DispatchMessage(&ms);
    }
Tworzymy pętlę odczytującą wiadomości z systemu, przeznaczone dla naszej aplikacji. Pętla wykonywana jest do momentu otrzymania wartości 0 z funkcji GetMessage  oznacza to, iż wszystkie wątki aplikacji zakończyły działanie i nie ma dla nich już wiadomości w kolejce.

Wewnątrz pętli wywołujemy dwie funkcje przetwarzamy otrzymaną wiadomość i przesyłamy ją do procedury okna.

    return ms.wParam;
}
Kończąc zwracamy parametr wParam (16-bitowy) zawarty w strukturze otrzymanej wiadomości przez ostatnie wywołanie funkcji GetMessage().

 

W programie wymień funkcję WndProc() na poniższą:

 

LRESULT CALLBACK WndProc(HWND h, UINT uMsg , WPARAM wP, LPARAM lP)
{
  switch(uMsg)
  {
    case WM_DESTROY :

      PostQuitMessage(0);
      break;

    // W procedurze okna obsługujemy wiadomość WM_PAINT, która jest wysyłana przy konieczności
    // przerysowania obszaru roboczego okna.

    case WM_PAINT :

      HDC         hdc;  // uchwyt kontekstu graficznego
      PAINTSTRUCT pps;  // struktura parametrów rysowania
      RECT        r;    // prostokąt obejmujący obszar roboczy okna

      hdc = BeginPaint(h, &pps);
      GetClientRect(h, &r);
      DrawText(hdc, "Witaj w świecie MS-Windows",-1, &r, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
      EndPaint(h, &pps);
      break;

    default:

      return DefWindowProc(h, uMsg, wP, lP);
  }
  return 0;
}

 

Skompiluj i uruchom program. Otrzymasz następujące okno:

 

obrazek

 

Program w systemie Windows nie może sobie tak po prostu coś narysować na ekranie. Ekran jest zasobem współdzielonym, zatem korzystanie z niego musi się odbywać wg ściśle określonych zasad. Okienko, po którym chcemy rysować może znajdować się w różnych stanach (np. może być zminimalizowane lub częściowo przykryte innym oknem itp.). Dlatego przy operacjach graficznych należy bezwzględnie wykorzystywać funkcje jądra graficznego GDI (ang. Graphic Device Interface – interfejs grafiki).

Jeśli chcemy coś narysować na obszarze roboczym okienka (ang. client area), najpierw musimy poprosić system Windows o udostępnienie dla tego obszaru uchwytu odpowiedniego kontekstu graficznego (ang. DC – device context).  Uchwyt kontekstu graficznego identyfikuje pewną wewnętrzną strukturę danych w systemie Windows, w której są umieszczone różne parametry graficzne. Uchwyt DC można otrzymać na kilka sposobów. W naszym programie wykorzystujemy do tego celu funkcję:

HDC BeginPaint
(
    HWND hwnd,
    LPPAINTSTRUCT lpPaint
);
HDC    typ danych określający uchwyt do kontekstu graficznego
HWND    typ danych dla uchwytu okna
LPPAINTSTRUCT    długi wskaźnik struktury PAINTSTRUCT
hwnd    uchwyt okna, po którym będziemy rysować
lpPaint    wskaźnik struktury PAINTSTRUC, w której funkcja BeginPaint zapisze informacje graficzne. Struktura ta posiada następującą definicję:
typedef struct tagPAINTSTRUCT
{ 
  HDC  hdc; 
  BOOL fErase; 
  RECT rcPaint; 
  BOOL fRestore; 
  BOOL fIncUpdate; 
  BYTE rgbReserved[32]; 
} PAINTSTRUCT, *PPAINTSTRUCT; 
hdc    uchwyt kontekstu graficznego urządzenia, które ma być użyte do rysowania (np. ekran, drukarka)
fErase    określa, czy przy rysowaniu musi być wymazane tło obszaru. Wartość tego pola jest niezerowa, jeśli aplikacja powinna wymazać tło. Obowiązek ten spoczywa na aplikacji, jeśli klasa okna została utworzona bez określenia pędzla tła.
rcPaint    określa strukturę RECT definiującą współrzędne narożników (lewy górny i prawy dolny) prostokąta, w którym konieczne jest malowanie. Prostokąt ten nie jest obszarem roboczym okna, lecz jego fragmentem, który musi być przerysowany. Współrzędne są określone względem lewego górnego narożnika obszaru roboczego okna.
fRestore
fIncUpdate
rgbReserved
   pola zarezerwowane do wewnętrznego użytku przez system Windows

Jeśli wywołanie funkcji się powiedzie, to zwrócony zostanie uchwyt kontekstu graficznego, który należy zachować w osobnej zmiennej, ponieważ będzie niezbędny przy operacjach graficznych na okienku. Jeśli wystąpi błąd, to funkcja zwróci wartość NULL.

 

Po pobraniu uchwytu kontekstu graficznego ustalamy obszar rysowania. W naszym programie nie bawimy się w szczegóły i pobieramy do struktury RECT współrzędne całego obszaru roboczego okna przy pomocy funkcji GetClientRect. W rzeczywistości w PAINTSTRUC.rcPaint system Windows umieszcza prostokąt obejmujący ten fragment obszaru roboczego okna, który uległ zmianie (np. przesunięto zakrywające go inne okno) i powinien zostać przerysowany (taki prostokąt nosi nazwę nieaktualnego – ang. invalid rectangle). Wykorzystanie tej informacji jest czasami dużo efektywniejsze od przerysowywania całego obszaru roboczego okna, lecz również i trudniejsze, gdyż musimy odpowiednio przeliczać położenia wszystkich obiektów graficznych.

Do wyrysowania tekstu na obszarze roboczym wykorzystujemy funkcję:

int DrawText
(
    HDC hdc,
    LPCTSTR lpString,
    int nCount,
    LPRECT lpRect,
    UINT uFormat
);
hdc    uchwyt kontekstu graficznego
lpString    wskaźnik tekstu do wyświetlenia.
nCount    określa liczbę znaków w wyświetlanym tekście. Jeśli parametr nCount ma wartość -1, to tekst musi być zakończony znakiem o kodzie 0.
lpRect    wskaźnik struktury RECT zawierającej współrzędne prostokąta, w którym tekst ma zostać sformatowany.
typedef struct _RECT
{ 
    LONG left;
    LONG top; 
    LONG right; 
    LONG bottom; 
} RECT, * PRECT; 
left    współrzędna x lewego górnego narożnika
top    współrzędna y lewego górnego narożnika
right    współrzędna x prawego dolnego narożnika
bottom    współrzędna y prawego dolnego narożnika
uFormat    określa sposób formatowania tekstu. Parametr ten może być złożony (za pomocą operatora | ) z poniższych stałych (opisujemy tylko niektóre wartości, resztę znajdziesz na stronach Microsoftu):
DT_BOTTOM    wyrównuje tekst do spodu prostokąta. Wartość ta jest wykorzystywana tylko po dołączeniu stałej DT_SINGLELINE.
DT_CENTER    wyśrodkowuje tekst w poziomie w obrębie prostokąta.
DT_END_ELLIPSIS    jeśli końcówka wiersza nie mieści się w prostokącie, zostaje obcięta i zastąpiona trójkropkiem.
DT_EXPANDTABS    rozwija znaki tabulacji. Standardową liczbą znaków dla tabulacji jest osiem.
DT_LEFT    wyrównuje tekst do lewej krawędzi prostokąta
DT_NOCLIP    rysuje tekst bez obcinania do rozmiaru prostokąta  opcja ta nieco przyspiesza działanie funkcji DrawText.
DT_RIGHT    wyrównuje tekst do prawej krawędzi prostokąta
DT_SINGLELINE    wyświetla tekst w postaci jednego wiersza  znaki końca wiersza i końca strony nie powodują złamania wiersza.
DT_TOP    wyrównuje tekst do górnej krawędzi prostokąta
DT_VCENTER    wyśrodkowuje tekst w pionie. Wartość ta jest używana tylko razem z wartością DT_SINGLELINE.
DT_WORDBREAK    automatycznie przenosi wyrazy do następnego wiersza, jeśli wykraczają poza krawędź prostokąta przekazanego w parametrze lpRect. Wiersz jest również łamany znakami końca wiersza i końca strony.

 

Przed zakończeniem obsługi wiadomości WM_PAINT musimy zwolnić uchwyt kontekstu graficznego. W tym celu wywołujemy funkcję EndPaint().

 

W tym miejscu możesz wypróbować różne funkcje graficzne API Win32.

Rysowanie punktów

COLORREF SetPixel
(
    HDC hdc,
    int X,
    int Y,
    COLORREF crColor
);

Funkcja rysuje punkt na ekranie.

hdc    uchwyt kontekstu graficznego
X,Y    współrzędne punktu na obszarze graficznym okna.
crColor    kolor piksela. Do utworzenia koloru posługujemy się makrem RGB, które przyjmuje trzy parametry określające kolejno składową czerwoną, zieloną i niebieską koloru. Każda ze składowych ma zakres od 0 (brak) do 255 (maksimum). Np.:
RGB(255,0,0)  kolor intensywnie czerwony
RGB(0,255,0) 
 kolor intensywnie zielony
RGB(255,0,255) 
 kolor fioletowy

Funkcja zwraca kod koloru ustawionego piksela. Kod ten może się różnić od parametru crColor, jeśli w bieżącym trybie graficznym nie istnieje dokładny odpowiednik. Również szybkość działania nie jest specjalnie imponująca.

LRESULT CALLBACK WndProc(HWND h, UINT uMsg , WPARAM wP, LPARAM lP)
{
  switch(uMsg)
  {
    case WM_DESTROY :

      PostQuitMessage(0);
      break;

    // W procedurze okna obsługujemy wiadomość WM_PAINT, która jest wysyłana przy konieczności
    // przerysowania obszaru roboczego okna.

    case WM_PAINT :

      HDC         hdc;  // uchwyt kontekstu graficznego
      PAINTSTRUCT pps;  // struktura parametrów rysowania
      RECT        r;    // prostokąt obejmujący obszar roboczy okna
      int cw,ch;

      hdc = BeginPaint(h, &pps);
      GetClientRect(h, &r);

      cw = r.right - r.left;  // szerokość obszaru klienta
      ch = r.bottom - r.top;  // wysokość obszaru klienta

      for(int y = 0; y < ch; y++)
        for(int x = 0; x < cw; x++)
        {
          COLORREF c = RGB(255-255*x/cw,(255-255*y/ch)/2,255*y/ch);
          SetPixel(hdc,x,y,c);
        }

      EndPaint(h, &pps);
      break;

    default:

      return DefWindowProc(h, uMsg, wP, lP);
  }
  return 0;
}

 

obrazek

 

Rysowanie Linii

WINGDIAPI BOOL WINAPI MoveToEx
(
    HDC hdc,
    int X,
    int Y,
    LPPOINT lpPoint
);

Funkcja ustawia początek linii.

hdc    uchwyt kontekstu graficznego
X,Y    współrzędne nowej pozycji, od której rozpocznie się rysowanie linii.
lpPoint    Adres struktury POINT, w której zostanie zapisana stara pozycja. Jeśli pole to zawiera NULL, to stara pozycja nie będzie zwracana.
typedef struct tagPOINT
{
    LONG x;
    LONG y;
} POINT;

Funkcja zwraca 0, jeśli operacja się nie powiodła.

 

WINGDIAPI BOOL WINAPI LineTo
(
    HDC hdc,
    int X,
    int Y
);

Funkcja rysuje linię od bieżącej pozycji (np. ustawionej przez wcześniejsze wywołanie MoveToEx lub LineTo) do punktu X,Y, który jednakże nie jest wypełniany kolorem. Po narysowaniu linii punkt X,Y staje się punktem startowym dla następnej linii. Funkcja zwraca 0, jeśli operacja się nie powiodła. Kolor linii jest zdefiniowany przez kolor bieżącego pióra.

hdc    uchwyt kontekstu graficznego
X,Y    współrzędne pozycji, do której będzie rysowana linia

 

HPEN CreatePen
( 
    int fnPenStyle, 
    int nWidth, 
    COLORREF crColor
);

Funkcja tworzy pióro.

fnPenStyle    styl pędzla. Możliwe są trzy wartości:

PS_SOLID – piórko będzie ciągłe.
PS_DASH – piórko będzie przerywane
PS_NULL – piórko będzie przezroczyste

nWidth    określa szerokość pióra. Dla wartości 0 piórko ma szerokość 1 piksela.
crColor    kolor pióra. Do utworzenia koloru posługujemy się makrem RGB.

Funkcja zwraca uchwyt do piórka. Jeśli zwróci wartość NULL, to operacja się nie powiodła.

 

HGDIOBJ SelectObject
(
    HDC hdc, 
    HGDIOBJ hgdiobj
); 

Funkcja wybiera obiekt do użytku w kontekście graficznym.

hdc    uchwyt kontekstu graficznego
hgdiobj    uchwyt obiektu, który ma zostać wybrany w danym kontekście. Tutaj przekazujemy wynik funkcji CreatePen..

Funkcja zwraca uchwyt do poprzedniego obiektu danego typu.

 

BOOL DeleteObject
(
    HGDIOBJ hObject
); 

Funkcja usuwa poprzednio utworzony obiekt, np. pióro. Jednakże nie wolno usuwać obiektu, który został wybrany w danym kontekście graficznym.

hObject    uchwyt usuwanego obiektu

 

LRESULT CALLBACK WndProc(HWND h, UINT uMsg , WPARAM wP, LPARAM lP)
{
  switch(uMsg)
  {
    case WM_DESTROY :

      PostQuitMessage(0);
      break;

    // W procedurze okna obsługujemy wiadomość WM_PAINT, która jest wysyłana przy konieczności
    // przerysowania obszaru roboczego okna.

    case WM_PAINT :

      HDC         hdc;  // uchwyt kontekstu graficznego
      PAINTSTRUCT pps;  // struktura parametrów rysowania
      RECT        r;    // prostokąt obejmujący obszar roboczy okna
      HGDIOBJ previous;
      int cw,ch,x,y;
 
      hdc = BeginPaint(h, &pps);
      GetClientRect(h, &r);

      previous = SelectObject(hdc,CreatePen(PS_SOLID,0,0)); // tworzymy nowe pióro i wybieramy je

      cw = r.right - r.left - 1;  // szerokość obszaru klienta
      ch = r.bottom - r.top - 1;  // wysokość obszaru klienta

      for(int i = 0; i < 30; i++)
      {
        DeleteObject(SelectObject(hdc,CreatePen(PS_SOLID,0,RGB(255 * i / 30, 0, 255 - 255 * i / 30))));
        x = i * cw / 30;
        y = i * ch / 30;

        MoveToEx(hdc,0,y,NULL);
        LineTo(hdc,x,ch);

        MoveToEx(hdc,0,ch-y,NULL);
        LineTo(hdc,x,0);

        MoveToEx(hdc,cw,y,NULL);
        LineTo(hdc,cw-x,ch);

        MoveToEx(hdc,cw,ch-y,NULL);
        LineTo(hdc,cw-x,0);
      }

      DeleteObject(SelectObject(hdc,previous)); // usuwamy pióro

      EndPaint(h, &pps);
      break;

    default:

      return DefWindowProc(h, uMsg, wP, lP);
  }
  return 0;
}

 

obrazek

 

Podsumowanie

Celem tych zajęć było pokazanie, że programowanie Windows to nie bajka. Trzeba bardzo dużo wiedzieć o tym systemie, aby stworzyć sensownie działający program. Dlatego właśnie powstały różne systemy ułatwiające zadanie programistów. Takim systemem szybkiego tworzenia aplikacji dla Windows jest środowisko Borland C++ Builder, gdzie okienko z napisem na środku można otrzymać bez pisania ani jednej linijki kodu:

 

Utwórz projekt okienkowy w Borlandzie.

W oknie Object Inspector dla Form1 ustaw własności:

Caption = Nasze pierwsze okno
Height = 200
Width = 300

Na okienku umieść panel.

W oknie Object Inspector dla panelu ustaw własności:

Align = alClient
BevelOuter = vbNone
Caption = Witaj w świecie MS-Windows
Color = clWhite

Skompiluj i uruchom program. Otrzymasz:

 

obrazek

 

Osobnym problemem jest tworzenie aplikacji przenośnych na inne platformy. Jeśli napiszesz program dla Windows, to jego przeróbka dla systemu Linux może być dosyć kosztowna czasowo. Z tego powodu opracowano różne dodatkowe biblioteki, które pozwalają tworzyć aplikacje niezależne od systemu operacyjnego komputera (oczywiście do pewnego stopnia niezależne). Istnieje tutaj kilka możliwości, np.:


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

©2024 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