Grafické aplikace ve Visual C++ (2.)


I přes všechny nevýhody GDI, je ve Windows takřka všudypřítomné. Jakékoliv kreslení ve Windows je dílem právě GDI. Myslím, že princip GDI je potřeba alespoň částečně chápat pro úplné pochopení DirectDraw. Ve skutečnosti na tom nic není. Potřebujete vědět, co jednotlivé funkce ve skutečnosti dělají.

2.1. Funkce OnDraw()

Funkce OnDraw() je jedna z nejdůležitěších funkcí GDI. Váš program jí automaticky volá pokaždé, když chce překreslit okno tzn. při změně velikosti, maximalizaci nebo minimalizaci okna atd. Jedná se o členskou funkci třídy pohledu CView. Přijímá jeden parametr a to je právě ukazatel na kontext zařízení (DC) vašeho okna. Funkce kontextu zařízení zapouzdřuje jiná třída CDC. Pomocí tohoto parametru můžete provádět veškeré kreslení do okna. Nejjednodušší řešení je, že zapíšete váš kód přímo do této funkce, případně si vytvoříte vlastní funkci, která bude mít jako parametr ukazatel na DC a budete ji volat z funkce OnDraw().

2.2. Jednoduchý příklad kreslení do okna

Vytvořte si standardní projekt SDI (Single Document Interface) MFC pomocí AppWizardu.
Najděte funkci OnDraw() ve třídě pohledu CView. Vaše třída pohledu se samozřejmě jmenuje podle jména projektu např. projekt Pepa bude mít třídu pohledu CPepaView.

Funkce CPepaView::OnDraw() po vygenerováni AppWizardem:

  void CPepaView::OnDraw(CDC* pDC)//ukazatel na kontext zařízení vašeho okna
  {
    CGDIDoc* pDoc = GetDocument();//ukazatel na váš dokument tzn. na CPepaDoc
    ASSERT_VALID(pDoc);//ověření pDoc
    // TODO: add draw code for native data here

  }

Přepište funkci následovně:

  void CPepaView::OnDraw(CDC* pDC)
  {
     CGDIDoc* pDoc = GetDocument();
     ASSERT_VALID(pDoc);

     //deklarace promenych
    CRect client, user;
    CPen pen;

    //tato funkce vrati velikost okna pohledu
    GetClientRect(&client);

    // vytvori pero o urcite sirce a barve
    pen.CreatePen(PS_SOLID, 5, RGB(255,0,0));

    //od kazde souradnice odectu 25 pixelu
    user.top = client.top + 25;
    user.left = client.left + 25;
    user.right = client.right - 25;
    user.bottom = client.bottom - 25;

    pDC->SelectObject(&pen); //vybere nase pero do kontextu zarizeni
    pDC->Rectangle(&user); // tato funkce nakresli obdelnik

    //napise text doprostred okna
    pDC->TextOut(client.right / 2, client.bottom / 2, "GDI v praxi");

  }

Program nakreslí do okna červený obdélník a doprostřed napíše text. Do kontextu zařízení vždy kreslíte právě vybraným tzv. GDI objektem jako jsou pera, štětce, fonty, bitmapy atd. Tyto objekty jsou v MFC za prvé předem připraveny jako Stock Objects a za druhé si je můžete vytvořit samy, což je samozřejmě běžnější způsob práce z GDI objekty.

2.3. GDI objekty

GDI objekty zapouzdřuje několik tříd (my si uvedeme jen ty nejběžnější) :


Pero

CPen - třída, ze které se vytvoří pero o daném typu, šířce a barvě. Perem kreslíme čáry různé tloušťky a typu.

Postup vytvoření pera :

  1. Vytvoříte objekt typu CPen

  2. Zavoláte členskou funkci třídy CPen::CreatePen()

Takto vypada deklarace funkce pro vytvoření pera:
BOOL CreatePen(int nPenStyle, int nWidth, COLORREF crColor);

  1. nPenStyle - styl pera může být například:

    • PS_SOLID - plná čára

    • PS_DOT - tečkovaná čára

    • PS_DASH - čárkovaná čára

    • PS_DASHDOT - čerchovaná čára


  2. nWidth - šířka pera je celočíselná hodnota v pixelech (výše uvedené typy per kromě PS_SOLID fungují pouze je-li šířka pera 1 pixel).

  3. crColor - barva pera. Tento parametr se předává pomocí makra RGB, které přijímá tři hodnoty barev RGB (Red, Green, Blue) v rozsahu 0 - 255 např. když napíšete RGB(255,0,0) funkce vytvoří sytě červené pero.

Tento objekt jsme použili ve výše uvedeném příkladu.


Štětec - Brush

CBrush - třída, která vytvoří štětec pro vykreslování velkých ploch v okně libovolnou barvou nebo vzorem.

Postup vytvoření štětce :

  1. Vytvoříte objekt typu CBrush

  2. Zavoláte členskou funkci třídy CBrush::CreateSolidBrush(). Funkce CreateSolidBrush() vytvoří štětec o jedné barvě tzn. že má jeden parametr, který se zadává podobně jako u pera pomocí makra RGB.

Font

CFont - třída, která vytvoří font pro psaní do okna. Můžete si vytvořit zcela vlastní font, ale myslím, že spíše využijete fontů, které jsou nainstalované ve Windows.

Postup vytvoření fontu :

  1. Vytvoříte objekt typu CFont

  2. Zavoláte členskou funkci třídy CFont::CreatePointFont(), která má následující parametry :

    1. velikost fontu v desetinách bodu tzn. pokud chcete písmo o velikosti 12 bodů musíte zadat tento parametr 120

    2. tvář (face) fontu. Tento parametr je řetězec, který obsahuje jméno vybraného fontu tzn. pokud chcete Arial dosadíte "Arial" nebo "Times New Roman" pro Times New Roman. Pokud není vámi vybrané písmo ve Windows program použije standardní písmo.



Dalšími GDI objekty jsou například CBitmap, CPalette nebo CRgn.

2.4. Třída CDC

Poté, co si vytvoříte svůj GDI objekt musíte ho vybrat do kontextu zařízení vašeho okna. To obstará funkce třídy CDC::SelectObject(), která přijímá ukazatel na váš GDI objekt. Od této chvíle se bude kreslit vaším perem nebo štětcem a psát vaším fontem. Důležité je, že může být vybrán jen jeden objekt. Pokud tedy zavoláte opakovaně tuto funkci, předchozí objekt se vyjme a vloží se jiný. Funkce SelectObjekt() vrací ukazatel předešlý vybraný objekt GDI.

Třída CDC obsahuje velké množství funkcí pro kreslení, takže je nemohu všechny popsat.

Funkce pro objekt CPen:

CPoint MoveTo( int x, int y );
CPoint MoveTo( POINT point );

Tyto dvě funkce pouze přesouvají aktuální pozici odkud se bude příště kreslit. Vrací předešlou pozici.

BOOL LineTo( int x, int y );
BOOL LineTo( POINT point );

Tyto dvě funkce kreslí do okna právě vybraným perem čáru. Kreslí se z aktuální pozice předem nastavené funkcí MoveTo() na pozici určenou parametry. Aktuální souřadnice se přesouvá tam, kde skončí pero s kreslením. Vrací nenulovou hodnotu pokud je úspěšná jinak 0.


Poznámka: Všimněte si, že každá funkce má dvě varianty, to umožňuje C++ díky přetěžování funkcí (overloading). Vidíte, že první varianta přijímá jednoduché typy, zatímco druhá varianta objekt POINT respektive RECT. To jsou objekty Win32 API. Ekvivalentní objekt v MFC je CPoint respektive CRect. Tento objekt za prvé uchovává informaci o souřadnici bodu respektive obdélníku a za druhé obsahuje mnoho členských funkcí, které podstatně zjednodušují práci programátora.



BOOL Rectangle( int x1, int y1, int x2, int y2);
BOOL Rectangle( LPRECT lpRect);

Tyto funkce nakreslí obdélník do okna vybraným perem. Jako parametry můžete zadat čtyř souřadnice tzn. horní levý roh a dolní pravý roh nebo předáte ukazatel na objekt typu CRect respektive RECT. Vrací nenulovou hodnotu pokud je úspěšná jinak 0.

BOOL Ellipse ( int x1, int y1, int x2, int y2 );
BOOL Ellipse ( LPRECT lpRect );

Tyto funkce kreslí obecně elipsu, ale dá se s ní samozřejmě nakreslit i kružnice. Má čtyři parametry, které jsou vlastně stejné jako u předešlé funkce. Vrací nenulovou hodnotu pokud je úspěšná jinak 0.

Funkce pro objekt CBrush

BOOL FillRect ( LPRECT lpRect, CBrush * pBrush );

Tato funkce trochu popírá, co jsme si řekli o vkládání objektů, protože přijímá parametr typu CBrush, což je jak jistě víte GDI objekt. No nevím, jak to vývojáři MFC mysleli. Každopádně tato funkce funguje tak, že vyplní obdélníkovou oblast právě tím štětcem, který je zadán jako parametr a ne tím, který je vybrán v DC. První parametr je ukazatel na objekt typu CRect respektive RECT.

BOOL FloodFill ( int x, int y , COLORREF crColor);

Tato funkce je velmi podobná funkci ze starého Pascalu. Začne vyplňovat plochu od určeného bodu, dokud nenarazí barvu určenou parametrem crColor. První dva parametry jsou ten bod a druhy ta barva, která se opět zadává pomocí makra RGB (viz výše). Vyplňuje právě vybraným štětcem. Vrací nenulovou hodnotu pokud je úspěšná jinak 0.

Funkce pro objekt CFont

BOOL TextOut (int x, int y, CString& str );

Tato funkce napíše text v bodě o souřadnicíchx a y. Samotný text je uložen ve třetím parametru typu CString respektive referenci na CString. Píše se právě vybraným fontem a návratová hodnota je stejná jako ve všech předchozích případech.

Třída CDC má spoustu dalších funkcí. Pokud byste měli nějaký speciální zájem, stačí napsat.

2.5. Příklad

Připravil jsem pro vás jednoduchý příklad, který použije všechny výše popsané funkce. Je to standardní aplikace MFC AppWizard [exe]. Přesný postup, jak vytvořit tento typ aplikace najdete ve třetím kurzu tohoto dílu a vy akorát upravíte funkci OnDraw(). Tento příklad si můžete stáhnout z CD zde nebo v sekci Downloads.

void CGDIView::OnDraw(CDC* pDC)
{
    CGDIDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    //
    // Procvicujeme funkce GDI
    CRect rcClient;
    // Ulozime si velikost okna
    GetClientRect(&rcClient);
    //
    // 1. Vytvorme si objekt pera CPen
    CPen penBlue;
    penBlue.CreatePen(PS_SOLID, 2, RGB(0, 0, 255));
    // 2. Vytvorme si objekt stetce CBrush
    CBrush brushRed, brushGreen;
    brushRed.CreateSolidBrush(RGB(255, 0, 0));
    brushGreen.CreateSolidBrush(RGB(0, 255, 0));
    // 3. Do tretice si vytvorime font CFont
    CFont fontTimes;
    fontTimes.CreatePointFont(120, "Times New Roman");
    //
    // Vybereme pero
    pDC->SelectObject(penBlue);
    // Nyni muzeme kreslit modrym perem
    // 1. Vytvorime diagonalni caru
    pDC->MoveTo(0, 0);
    pDC->LineTo(rcClient.right, rcClient.bottom);
    // 2. Nakreslime obdelnik
    pDC->Rectangle(rcClient.left + 25, rcClient.top + 25,
        rcClient.right - 25, rcClient.bottom - 25);
    // 3. Nakreslime elipsu uprostred okna

    pDC->Ellipse(rcClient.left + 50, rcClient.top + 50,
        rcClient.right - 50, rcClient.bottom - 50);
    // 4. Pouzijeme funkci FillRect() k vybarveni obdelnicku
    // Vsimnete si, ze nevybirame stetec!!!
    pDC->FillRect(CRect(150, 150, 200, 200), &brushGreen);
    //
    // Nyni jiz musime vybrat stetec do DC
    pDC->SelectObject(brushRed);
    // 5. Pouzijeme funkci FloodFill() k vybarveni obdelnicku
    pDC->FloodFill(30, 30, RGB(0, 0, 255));
    //
    // 6. Nakonec vybereme font do DC a napiseme text doprostred okna
    pDC->SelectObject(fontTimes);
    pDC->TextOut(rcClient.right / 2, rcClient.bottom / 2, "GDI v praxi");
    //
}


Poznámka:

  • Zkuste si zmenšit či zvětšit okno. Všimněte si, že obsah okna se nehezky překresluje pokaždé, když změníte velikost okna. To je dáno tím, že funkce OnDraw() je volána pokaždé, když se má okno překreslit a také samozřejmě tím, že GDI je pomalé.

  • Jak vidíte u funkce FillRect(), není potřeba vybírat žádný štětec, protože se k vybarvení použije štětec, který je dán v druhém parametru.

  • Vzápětí ale vidíte, že u funkce FloodFill() již štětec vybrat musíme, jinak se použije tzv. NULL BRUSH, který nenakreslí vůbec nic.

  • Text, který má být uprostřed okna, vlastně není přesně uprostřed. Zkuste program upravit tak, aby text byl pokaždé uprostřed. Je nutné do souřadnic textu zahrnout také velikost samotného textu.


Takto vypadá okno naší aplikace:



2.6. Závěr

Tato část vás tedy seznámila s úplnými základy GDI a měli byste si alespoň něco z toho pamatovat, ačkoliv to v DirectDraw přímo nepoužijeme. V DirectDraw budeme tu a tam používat kontexty zařízení, ale to uvidíte až v příští lekci. V příští lekci už začneme skutečné DirectDraw. Pokud byste měli nějaké speciální požadavky nebo chtěli poradit, můžete mi napsat.

Těším se příště nashledanou.

Jiří Formánek