Koło informatyczne 2012

Wskaźniki i elementy składowe struktur/klas

Na potrzeby tego rozdziału utwórz projekt konsoli:
  1. Z menu wybierz opcję FileNewOther...
  2. Z okienka dialogowego wybierz opcję Console Wizard:

    obrazek
  3. W następnym oknie dialogowym ustaw opcje jak na poniższym obrazku i kliknij przycisk OK:

    obrazek

Konsola jest znakowym trybem pracy programu i nie wykorzystuje systemu graficznego Windows. W edytorze zostanie utworzony plik projektu konsoli.

 

Zmienne mogą zawierać bezpośrednie dane. Wpisz do okna edytora poniższy program, skompiluj go i uruchom:

 

#include <iostream>

using namespace std;

//---------------------------------------------------------------------------

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
int main(int argc, char* argv[])
{
  int a;

  a = 10;

  cout << a << endl;

  system("pause");

  return 0;
}
//---------------------------------------------------------------------------

 

Program tworzy zmienną a, wpisuje do niej liczbę 10, a następnie wyświetla w oknie konsoli jej zawartość. Funkcja system() wykonuje polecenie pause. które czeka na naciśnięcie dowolnego klawisza. Bez niej okno konsoli zostałoby prawie natychmiast zamknięte po zakończeniu programu i nic byśmy nie zobaczyli.

 

Zmienne mogą również zawierać adresy pamięci, w których przechowywane są dane. Takie zmienne nazywamy wskaźnikami lub wskazaniami (ang. pointer). Wprowadź do edytora poniższy program:

 

#include <iostream>

using namespace std;

//---------------------------------------------------------------------------

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
int main(int argc, char* argv[])
{
  int a, b, *p;

  a = 10; b = 99;

  p = &a; // w p jest adres zmiennej a

  cout << *p << endl;

  p = &b; // w p jest adres zmiennej b

  cout << *p << endl;

  system("pause");

  return 0;
}
//---------------------------------------------------------------------------

 

W programie zmienna p  jest wskaźnikiem, który przechowuje adresy danych. Wskaźnik tworzymy dodając przed jego nazwę znak *. Dostęp do przechowywanych przez wskaźnik danych następuje poprzez *p. W programie tworzymy dwie zmienne a, b  oraz wskaźnik p. W zmiennych a  i b  umieszczamy dane. Następnie do p  wprowadzamy adres zmiennej a. Od tego momentu odwołanie do *p  jest równoważne z odwołaniem do a. Następnie zawartość p  na adres zmiennej b. Odwołanie do *p  oznacza odwołanie do b.

 

Do edytora wprowadź kolejny program:

 

#include <iostream>

using namespace std;

//---------------------------------------------------------------------------

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
int main(int argc, char* argv[])
{
  int a, b, * p;

  p = &a;   // w p jest adres zmiennej a

  *p = 10;  // do a wprowadzamy 10 poprzez wskaźnik p

  p = &b;   // w p jest adres zmiennej b

  *p = 99;  // do b wprowadzamy 99 poprzez wskaźnik p

  cout << a << endl
       << b << endl;

  system("pause");

  return 0;
}
//---------------------------------------------------------------------------

 

Tym razem wykorzystujemy wskaźnik do umieszczenia danych pod wskazywanym przez niego adresem.

 

Wskaźniki wykorzystujemy często do przedzielania pamięci w sposób dynamiczny. Wprowadź do edytora program:

 

#include <iostream>

using namespace std;

//---------------------------------------------------------------------------

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
int main(int argc, char* argv[])
{
  int *a, *b;

  a = new int; // tworzymy daną int i jej adres umieszczamy w a
  b = new int; // tworzymy daną int i jej adres umieszczamy w b

  *a = 10; // do utworzonej danej wprowadzamy 10
  *b = 99; // do utworzonej danej wprowadzamy 99

  cout << *a << endl
       << *b << endl;

  delete a; // usuwamy daną z pamięci
  delete b; // usuwamy daną z pamięci

  system("pause");

  return 0;
}
//---------------------------------------------------------------------------

 

Powyższy program tworzy dwa wskaźniki a i b. Następnie przydziela im pamięć na dane za pomocą operatora new. Składnia jest następująca:

 

wskaźnik = new typ danych;

 

Operator new tworzy w pamięci obszar o takim rozmiarze, aby można było w nim pomieścić daną danego typu. Po utworzeniu tego obszaru zwraca jego adres. Zwrócony adres instrukcja przypisania umieszcza we wskaźniku. Teraz poprzez *wskaźnik możemy się odwoływać do zawartości tego obszaru.

 

Zmienne w C++ mogą być złożone, tzn. mogą naraz przechowywać więcej niż jedną daną. Wpisz do edytora poniższy program:

 

#include <iostream>

using namespace std;

//---------------------------------------------------------------------------

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused

// definiujemy strukturę

struct struktura
{
  int a,b;  // elementy składowe struktury
};

int main(int argc, char* argv[])
{
  struktura s; // tworzymy zmienną strukturalną

  s.a = 10;  // do pola a struktury s wprowadzamy 10
  s.b = 99;  // do pola b struktury s wprowadzamy 99

  cout << s.a << endl
       << s.b << endl;

  system("pause");

  return 0;
}
//---------------------------------------------------------------------------

 

W programie definiujemy typ strukturalny o nazwie struktura. Zmienna tego typu zawiera dwa pola danych typu int o nazwach a  i b. Tworzymy zmienną s  typu struktura. Dostęp do pól a  i b  uzyskujemy w zmiennej za pomocą operatora kropka.

 

Wskaźniki mogą również zawierać adresy struktur. Wpisz do edytora poniższy program:

 

#include <iostream>

using namespace std;

//---------------------------------------------------------------------------

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused

// definiujemy strukturę

struct struktura
{
  int a,b;  // elementy składowe struktury
};

int main(int argc, char* argv[])
{
  struktura * p;     // tworzymy wskaźnik do struktury

  p = new struktura; // tworzymy dynamiczną strukturę

  p->a = 10;  // do pola a struktury wprowadzamy 10
  p->b = 99;  // do pola b struktury wprowadzamy 99

  cout << p->a << endl
       << p->b << endl;

  delete p;  // usuwamy strukturę z pamięci

  system("pause");

  return 0;
}
//---------------------------------------------------------------------------

 

W programie wskaźnik p  wskazuje obiekty typu struktura - w C++ wskaźniki również posiadają typy. Strukturę tworzymy dynamicznie w pamięci i jej adres umieszczamy we wskaźniku p. Dostęp do pól a  i b  struktury następuje poprzez operator ->, a nie kropka!

 

Zapamiętaj:

  1. Jeśli p  jest wskaźnikiem, to *p  jest elementem wskazywanym.
  2. Jeśli s  jest strukturą, to s.a  jest polem a tej struktury.
  3. Jeśli p  jest wskaźnikiem struktury, to p->a  jest polem a tej struktury.

Canvas - piksele

Pisząc programy w środowisku graficznym, musimy znać sposoby tworzenia własnej grafiki. Wiele komponentów w systemie Borland C++ Builder posiada własność (ang. property) o nazwie Canvas. Jest to obiekt graficzny, który reprezentuje powierzchnię graficzną komponentu, zatem wszystko to, co się znajduje na ekranie w miejscu zajętym przez komponent. Dostęp do Canvas następuje poprzez wskaźnik:

 

komponent -> Canvas

 

Dla funkcji składowych okna głównego wystarczy samo Canvas - funkcje składowe, które są częścią klasy okna (w dużym uproszczeniu klasa jest "strukturą", która oprócz pól danych może zawierać własne funkcje składowe) posiadają bezpośredni dostęp do wszystkich pól tej klasy oraz jej funkcji składowych. Jeśli jednak chcesz się odwołać do Canvas okna spoza klasy (czyli z funkcji zewnętrznej, która nie jest funkcją składową klasy), to musisz zastosować składnię:

 

okno -> Canvas

 

A dla komponentów tego okna:

 

okno -> komponent -> Canvas

 

Jest to spowodowane tym, iż klasa jest obiektem dynamicznym, do którego uzyskujemy dostęp poprzez wskaźnik okna. Również wszystkie komponenty są tworzone dynamicznie w pamięci i dostęp do ich pól oraz funkcji składowych następuje poprzez wskaźniki, dlatego używamy operatorów ->.

 

Na potrzeby tego rozdziału stwórz projekt okienkowy. Następnie w oknie Object Inspector wybierz zakładkę Events i kliknij dwukrotnie myszką obok zdarzenia onPaint. Zdarzenie to powstaje, gdy okno musi przerysować swoją zawartość. W edytorze powstanie funkcja obsługi zdarzenia onPaint dla okna głównego aplikacji:

 

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{

}
//---------------------------------------------------------------------------

 

Dostęp do poszczególnych punktów na powierzchni Canvas uzyskujemy poprzez własność Pixels, która jest jakby tablicą dwuwymiarową (patrz na wyjaśnienie na końcu podrozdziału). Elementy tej tablicy odnoszą się do pikseli na powierzchni graficznej. Element:

 

Canvas->Pixels[x][y]

 

odnosi się do piksela leżącego w wierszu y i w kolumnie x na powierzchni graficznej. Współrzędne (0,0) odnoszą się do lewego górnego narożnika powierzchni. Zwiększając x, przesuwamy się w kierunku prawej krawędzi. Zwiększając y, przesuwamy się w dół. Na początek wprowadź do edytora poniższy kod:

 

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
  for(int i = 0; i < 200; i++)
    Canvas->Pixels[i][i] = 0;
}
//---------------------------------------------------------------------------

 

Gdy uruchomisz program, otrzymasz ukośną linię biegnącą w prawo i w dół od górnego narożnika okna. Linia jest w kolorze czarnym, ponieważ w jej pikselach umieściliśmy wartość 0. Do określania koloru piksela możesz użyć gotowej stałej. Oto niektóre z nich:

 

Nazwa stałej Kolor
clBlack czarny
clBlue niebieski
clFuchsia fiołkowy
clGray szary
clMaroon kasztanowy
clOlive oliwkowy
clRed czerwony

 

Opis tych stałych znajdziesz w pliku pomocy dla środowiska Borland: wystarczy w edytorze wpisać jedną ze stałych koloru, np. clBlack, wskazać ją kursorem tekstowym i wcisnąć klawisz F1.

Wypróbuj te stałe w programie.

Drugim sposobem określania koloru jest podanie intensywności kolorów podstawowych R - czerwonego, G - zielonego i B - niebieskiego. Tutaj musimy potraktować kolor piksela jako daną binarną składającą się z trzech bajtów. Każdy bajt określa intensywność barwy podstawowej:

 

Bajt B Bajt G Bajt R

 

Bajty te najlepiej przedstawiać w postaci szesnastkowej - każdy będzie się składał z dwóch cyfr od 00 (wartość najmniejsza) do ff (wartość największa):

 

0xff0000 - kolor niebieski
0x00ff00 - kolor zielony
0x0000ff - kolor czerwony
0x00ffff - kolor żółty
0xffffff - kolor biały

 

Wypróbuj ten sposób w naszym programie.

 

Innym sposobem określania koloru jest wykorzystanie makra RGB(r,g,b), gdzie r - składowa czerwona, g - składowa zielona i b - składowa niebieska.

Zastąp w edytorze funkcję FormPaint() nową:

 

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
  for(int y = 0; y < 256; y++)
    for(int x = 0; x < 256; x++)
      Canvas->Pixels[x][y] = RGB(x,0,0);
}
//---------------------------------------------------------------------------

 

Otrzymasz kwadrat wypełniony gradientem:

 

obrazek

 

Pobaw się, wpisując do programu różne wersje makra RGB():

 

RGB(0,x,0)
RGB(0,0,y)
RGB(0,x,y)
RGB(y,x,255-y)

 

Zastanów się, jak narysować wypełnienie całej powierzchni okna aplikacji.

 

Uwaga techniczna

Dostęp do poszczególnych pikseli nie jest w Canvas specjalnie efektywny. Udostępniona tablica Pixels w rzeczywistości wcale nie jest rzeczywistą powierzchnią okna aplikacji. Każdy zapis do elementów tej tablicy powoduje wywołanie odpowiedniej funkcji - SetPixel(), która otrzymuje współrzędne piksela oraz jego kolor. Funkcja ta oblicza następnie adres piksela na rzeczywistej powierzchni okna i umieszcza tam piksel o podanym kolorze. Wymaga to wielu operacji, które czynią cały proces dosyć wolnym. Szybkie rysowanie na poziomie pikseli zapewnią ci tylko funkcje API systemu Windows.

 

Canvas - linie

Do prostej obsługi rysowania linii Canvas udostępnia nam następujące dwie funkcje składowe:

 

Canvas -> MoveTo(x,y)  -  ustawia początek linii w punkcie (x,y). Na powierzchni graficznej nic nie jest rysowane.
Canvas -> LineTo(x,y)  - rysuje linię od ostatnio ustawionego punktu do (x,y), lecz punktu końcowego nie ustawia. Punkt (x,y) staje się punktem początkowym dla następnej linii rysowanej za pomocą tej funkcji. Umożliwia to tworzenie łamanych.

 

Kolor rysowanych linii definiuje własność Canvas -> Pen -> Color. Kolor możemy określić w taki sam sposób jak dla pikseli. Standardowym kolorem rysowania linii jest kolor czarny.

Wpisz do edytora poniższy kod dla funkcji FormPaint():

 

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
  for(int i = 0; i < 256; i++)
  {
     Canvas->Pen->Color = RGB(i,i,0);  // kolor linii, w każdym obiegu inny
     Canvas->MoveTo(i,i);              // punkt początkowy trójkąta nr 1
     Canvas->LineTo(512-i,i);          // dolny bok
     Canvas->LineTo(512-i,512-i);      // prawy bok
     Canvas->LineTo(i,i);              // przekątna
  }

  for(int i = 0; i < 256; i++)
  {
     Canvas->Pen->Color = RGB(i,0,i);  // kolor linii, w każdym obiegu inny
     Canvas->MoveTo(i,i);              // punkt początkowy trójkąta nr 2
     Canvas->LineTo(i,512-i);          // lewy bok
     Canvas->LineTo(512-i,512-i);      // górny bok
     Canvas->LineTo(i,i);              // przekątna
  }
}
//---------------------------------------------------------------------------

 

Rysowanie linii jest znacznie szybsze od rysowania pikseli.

 


 

Teraz pobawimy się rysowaniem linii. Na początek wielokąty foremne. Tworzymy je, wyznaczając punkty wierzchołkowe i łącząc te punkty za pomocą linii. Punkty wierzchołkowe można wyznaczyć w prosty sposób za pomocą funkcji trygonometrycznych. Wyobraźmy sobie, że chcemy narysować sześciokąt foremny. Rysujemy okrąg o środku w punkcie (0,0) i o promieniu r:

 

obrazek

Punkty (x,y) na okręgu wyliczamy wg wzoru:

 

obrazek

 

Sześciokąt foremny zbudujemy, wyznaczając na obwodzie tego koła sześć równoodległych punktów:

 

obrazek

 

Współrzędne tych punktów wyliczymy wg wzorów:

 

obrazek

gdzie i  = 0,1,2,...,5

 

Punkty łączymy liniami i powstaje sześciokąt foremny:

 

obrazek

 

Taki wielokąt nie zostałby w całości narysowany w oknie aplikacji, ponieważ jego środek wypadłby w lewym górnym narożniku powierzchni Canvas. Dlatego wielokąt zwykle chcemy rysować o środku w punkcie Sx, Sy. W tym celu musimy dokonać drobnej korekty wzorów:

 

obrazek

gdzie i  = 0,1,2,...,5

 

Podane wzory można prosto rozszerzyć na wielokąt foremny o dowolnej liczbie boków n, n > 2:

 

obrazek

gdzie i  = 0,1,2,...,n-1

 

Dodatkowo możemy wprowadzić do wzorów kąt początkowy, który określi położenie punktu P0:

 

obrazek

 

Nasze ostateczne wzory przyjmą zatem postać:

 

obrazek

gdzie i  = 0,1,2,...,n-1

 

Na podstawie tych wzorów napiszemy prostą aplikację, która w oknie aplikacji będzie rysowała zadany wielokąt foremny. Utwórz nowy projekt aplikacji w Borland C++ Builder. W oknie aplikacji umieść dwie etykiety tekstowe Label,, dwa pola edycyjne Edit oraz trzy przyciski Button. Poniżej podajemy własności dla poszczególnych elementów (do zmiany własności obiekt wybierasz na formie klikając go myszką lub w oknie Object TreeView, a następnie modyfikujesz jego własności w oknie Object Inspector):

Form1

BorderIconsbiMaximize = false
Okno nie będzie można powiększać na cały ekran.

BorderStyle = bsSingle
Użytkownik nie będzie mógł zmieniać rozmiarów okna przez przeciąganie myszką jego krawędzi.

Caption = Wielokaty foremne
Tytuł okna.

ClientHeight = 512
Wysokość obszaru graficznego okna. Taką wysokość będzie miała nasza powierzchnia rysunkowa Canvas.

ClientWidth = 512
Szerokość obszaru graficznego okna.

Color = clWhite
Okienka nie muszą zawsze być szare.

Name = frmPolygons
Pod tą nazwą będzie dostępna w programie zmienna klasy naszego okna.

Position = poScreenCenter
Okienko aplikacji będzie się pojawiało na środku ekranu.

Label1

Caption = Liczba boków:

Left = 32

Name= lbl1

Top = 456

Label2

Caption = Kąt początkowy:

Left = 32

Name= lbl2

Top = 480

Edit1

Left = 120

Name = edtN

Text = 6
Początkowa liczba boków.

Top = 456

Edit2

Left = 120

Name = edtFi

Text = 0
Początkowy kąt obrotu.

Top = 480

Button1

Caption = Rysuj

Default = true
Po naciśnięciu klawisza Enter ten przycisk będzie automatycznie wybierany

Left = 256

Name = btnPaint

Top = 464

Button2

Caption = Czyść

Left = 336

Name = btnClear

Top = 464

Button3

Caption = Koniec

Left = 416

Name = btnClose

Top = 464

 

Interfejs naszej aplikacji jest gotowy:

 

obrazek

 

Na początku programu dopisz fragmenty pokazane w kolorze czarnym:

 

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
#include <math.h>

TfrmPolygons *frmPolygons;
int n = 6;
int fi = 0;

//---------------------------------------------------------------------------

 

Teraz dodamy do niego obsługę zdarzeń. Wybierz frmPolygons i w Object Inspector kliknij zakładkę Events, a następnie kliknij dwukrotnie obok zdarzenia onPaint. Do utworzonej w edytorze funkcji obsługi zdarzenia wpisz poniższy kod:

 

void __fastcall TfrmPolygons::FormPaint(TObject *Sender)
{
  const double PI2 = 6.2831853;  // 2 pi
  int i,x,y;
  double f = PI2 * fi / 360;
  
  // wyznaczamy punkty na obwodzie koła o środku w punkcie (256,230)
  // i promieniu 200. Do pierwszego punktu przechodzimy poleceniem MoveTo(),
  // a pozostałe n punktów łączymy liniami za pomocą LineTo().

  for(i = 0; i <= n; i++)
  {
     x = 256 + 200 * cos(f + i * PI2 / n);
     y = 230 + 200 * sin(f + i * PI2 / n);
     if(i) Canvas->LineTo(x,y); else Canvas->MoveTo(x,y);
  }
  Canvas->Pixels[x][y] = 0; // zamykamy ostatni bok z pierwszym
}

 

W Object TreeView kliknij dwukrotnie btnPaint i do funkcji obsługi kliknięcia wpisz:

 

void __fastcall TfrmPolygons::btnPaintClick(TObject *Sender)
{
  n  = StrToInt(edtN->Text);  // odczytujemy liczbę boków
  fi = StrToInt(edtFi->Text); // odczytujemy kąt obrotu
  FormPaint(Sender);          // rysujemy wielokąt
}

 

To samo zrób z btnClear i wpisz do jego procedury obsługi kliknięcia kod:

 

void __fastcall TfrmPolygons::btnClearClick(TObject *Sender)
{
  Invalidate();
}

 

Funkcja Invalidate() powoduje unieważnienie treści okna, co z kolei spowoduje jego wyczyszczenie i przerysowanie od nowa, czyli wywołanie funkcji obsługi zdarzenia onPaint. Ponieważ najpierw treść okna jest czyszczona, to na jego powierzchni graficznej zostaje narysowany ostatnio wybrany wielokąt.

 

W podany powyżej sposób utwórz funkcję obsługi zdarzenia onClick dla btnClose i wprowadź do niej kod:

 

void __fastcall TfrmPolygons::btnCloseClick(TObject *Sender)
{
  Close();        
}

 

Aplikacja jest gotowa.

 

obrazek

 

Bardzo ciekawe figury powstają, gdy wszystkie wierzchołki wielokąta połączymy odcinkami. Wpisz do funkcji obsługi zdarzenia onPaint poniższy kod:

 

void __fastcall TfrmPolygons::FormPaint(TObject *Sender)
{
  const double PI2 = 6.2831853;  // 2 pi
  int i,j,x1,y1,x2,y2;
  double f = PI2 * fi / 360;

  for(i = 0; i < n; i++)
  {

     x1 = 256 + 200 * cos(f + i * PI2 / n); // punkt startowy linii
     y1 = 230 + 200 * sin(f + i * PI2 / n);
     for(j = i + 1; j < n; j++)
     {
       x2 = 256 + 200 * cos(f + j * PI2 / n); // kolejne punkty końcowe
       y2 = 230 + 200 * sin(f + j * PI2 / n);
       Canvas->MoveTo(x1,y1);
       Canvas->LineTo(x2,y2);
     }
  }
}

 

W oknie mogą wtedy powstawać takie oto "dzieła":

 

obrazek

 


Canvas - prostokąty i elipsy

 

Kolejna prosta aplikacja będzie rysowała prostokąty i elipsy. Są to figury, które posiadają wypełnienie. Do sterowania kolorem wypełnienia mamy własność:

 

Canvas -> Brush -> Color


Kody kolorów dla wypełnienia określa się identycznie jak dla pikseli lub linii.

Dodatkowo wykorzystamy prosty obiekt TRect definiujący prostokąt na powierzchni graficznej. Prostokąt definiujemy za pomocą parametrów przekazanych do jego konstruktora:

 

TRect r(left,top,right,bottom);

 

Konstruktor jest specjalną funkcją składową obiektu, która może być wywoływana w momencie tworzenia danego obiektu. Zadaniem konstruktora jest odpowiednie zainicjowanie wszystkich pól danych obiektu. Przy powyższej definicji otrzymujemy obiekt prostokąta, który definiuje pewien obszar na powierzchni graficznej. Parametry są następujące:

 

left  -  położenie lewej krawędzi
top  - położenie górnej krawędzi
right  - o 1 więcej niż położenie prawej krawędzi
bottom  - o 1 więcej niż położenie dolnej krawędzi (pamiętaj, że współrzędne graficzne na osi y rosną w dół)

 

Do rysowania prostokątów używamy funkcji:

 

Canvas -> Rectangle(r);

gdzie r  jest obiektem prostokąta.

 

Do rysowania elips używamy funkcji:

 

Canvas -> Ellipse(r);

gdzie r  jest prostokątem opisującym elipsę.

 

Utwórz nową aplikację w Borland C++ Builder. Na oknie głównym umieść dwa przyciski Button. Zmień własności wg poniższej listy:

Frame1

BorderIconsbiMaximize = true

BorderStyle = bsSingle

Caption = Kształty

ClientHeight = 512

ClientWidth = 512

Name = frmShapes

Position = poScreenCenter

Button1

Caption = Czyść

Left = 8

Name = btnClear

Top = 472

Button2

Caption = Koniec

Left = 88

Name = btnClose

Top = 472

 

Utwórz dla okna funkcję obsługi zdarzenia onMouseDown (w Object TreeView wybierz frmShapes, w Object Inspector wybierz zakładkę Events i kliknij dwukrotnie po prawej stronie zdarzenia onMouseDown). Zdarzenie to powstaje, gdy użytkownik kliknie w obiekt lewym lub prawym przyciskiem myszki. Powstanie następująca funkcja:

 

void __fastcall TfrmShapes::FormMouseDown(TObject *Sender, TMouseButton Button,
      TShiftState Shift, int X, int Y)
{

}

 

W parametrach funkcja otrzymuje:

 

Button  -  określa naciśnięty przycisk myszki:
mbLeft - lewy
mbRight - prawy
Shift  - zbiór określający użyte klawisze myszki oraz Alt, Shift i Ctrl na klawiaturze, Tutaj z tego nie korzystamy.
X, Y  - współrzędne kursora myszki w momencie powstania zdarzenia. Współrzędne odnoszą się do pozycji na powierzchni Canvas obiektu.

 

Do funkcji wpisz następujący kod:

 

void __fastcall TfrmShapes::FormMouseDown(TObject *Sender, TMouseButton Button,
      TShiftState Shift, int X, int Y)
{
  int rx = 10 + rand() % 60;    // wybieramy losowe szerokości
  int ry = 10 + rand() % 60;
  TRect r(X-rx,Y-ry,X+rx,Y+ry); // tworzymy prostokąt r o środku w X,Y

  Canvas->Brush->Color = RGB(rand()%256,rand()%256,rand()%256); // kolor wypełnienia
  Canvas->Pen->Color   = RGB(rand()%256,rand()%256,rand()%256); // kolor linii
  if(Button == mbLeft)       Canvas->Rectangle(r);  // dla lewego przycisku prostokąt
  else if(Button == mbRight) Canvas->Ellipse(r);    // a dla prawego elipsa
}

Teraz kliknij dwukrotnie w Object TreeView na btnClear i w edytorze wpisz kod:

 

void __fastcall TfrmShapes::btnClearClick(TObject *Sender)
{
  Invalidate();
}

 

Wywołanie funkcji Invalidate() powoduje unieważnienie treści okna, które zostanie narysowane od nowa, mażąc narysowaną wcześniej grafikę.

Kliknij dwukrotnie w Object TreeView na btnClose i w edytorze wpisz kod:

 

void __fastcall TfrmShapes::btnCloseClick(TObject *Sender)
{
  Close();
}

Po uruchomieniu aplikacji kliknięcie lewym przyciskiem w okienko powoduje narysowanie prostokąta, a kliknięcie przyciskiem prawym powoduje narysowanie elipsy. Ponieważ kolory wypełnienia i linii są pseudolosowe, za każdym razem będą zwykle inne. Również wielkość prostokątów i elips będzie się zmieniała.

 

obrazek

 

Funkcja składowa:

 

Canvas -> FrameRect(r);

gdzie r  jest prostokątem

 

Rysuje ramkę o pustym wnętrzu. Do rysowania linii ramki wykorzystywany jest obiekt Canvas -> Brush.

Elipsę bez wnętrza narysujemy za pomocą funkcji:

 

Canvas -> Arc(x1,y1,x2,y2,x3,y3,x4,y4);

 

Funkcja ta wymaga przekazania jej współrzędnych czterech punktów:

 

x1,y1 i x2,y2 określają prostokąt, w którym będzie rysowana elipsa (lewy górny i prawy dolny narożnik):

 

obrazek

 

Punkt x3,y3 oraz środek elipsy wyznaczają linię, której przecięcie z elipsą określa punkt startowy, od którego będzie rysowana elipsa. Kierunek rysowania jest przeciwny do kierunku ruchu wskazówek zegara.

Punkt x4,y4 oraz środek elipsy wyznaczają linię, której przecięcie z elipsą określi punkt końcowy jej linii. Jeśli punkty x3,y3 i x4,y4 są różne, to będzie narysowany tylko fragment elipsy. Jeśli są takie same, elipsa zostanie narysowana w całości.

 

obrazek

 

 

W Naszym programie zastąp funkcję obsługi zdarzenia onMouseDown poniższym kodem:

 

void __fastcall TfrmShapes::FormMouseDown(TObject *Sender, TMouseButton Button,
      TShiftState Shift, int X, int Y)
{
  int rx = 10 + rand() % 60;
  int ry = 10 + rand() % 60;
  TRect r(X-rx,Y-ry,X+rx,Y+ry);

  Canvas->Brush->Color = RGB(rand()%256,rand()%256,rand()%256); // kolor ramki
  Canvas->Pen->Color   = RGB(rand()%256,rand()%256,rand()%256); // kolor elipsy
  if(Button == mbLeft)  Canvas->FrameRect(r);
  if(Button == mbRight) Canvas->Arc(r.Left,r.Top,r.Right,r.Bottom,r.Left,r.Bottom,r.Left,r.Bottom);
}

 

obrazek

Podsumowanie

Canvas - powierzchnia rysunkowa.

Canvas -> Pixels[x][y] - piksel w punkcie x,y  powierzchni rysunkowej.

Canvas -> MoveTo(x,y); - ustawia początek linii w punkcie x,y.

Canvas -> LineTo(x,y); - rysuje linię od punktu początkowego do x,y. Punkt x,y  nie jest rysowany, lecz staje się początkiem dla kolejnej linii.

Canvas -> Pen -> Color - własność określająca kolor rysowanych linii i łuków.

Canvas -> Brush -> Color - własność określająca kolor wypełnienia

Canvas -> Rectangle(r); - rysuje prostokąt wewnątrz prostokąta r. Linia brzegowa w kolorze Canvas->Pen->Color, wnętrze w kolorze Canvas->Brush->Color.

Canvas -> Ellipse(r); - rysuje owal wewnątrz obszaru określonego przez prostokąt r. Linia brzegowa w kolorze Canvas->Pen->Color, wnętrze w kolorze Canvas->Brush->Color.

Canvas -> FrameRect(r); - rysuje ramkę wewnątrz obszaru określonego przez prostokąt r. Linia brzegowa w kolorze Canvas->Brush->Color.

Canvas -> Arc(x1,y1,x2,y2,x3,y3,x4,y4); - rysuje łuk wewnątrz prostokąta określonego przez dwa pierwsze punkty x1,y1 i x2,y2. Środek prostokąta i punkt x3,y3 wyznaczają linię, której przecięcie z elipsą określa punkt startowy - łuk elipsy jest rysowany w kierunku przeciwnym do ruchu wskazówek zegara. Środek prostokąta i punkt x4,y4 wyznaczają linię, której przecięcie z elipsą określa punkt końcowy łuku. Jeśli punkty x3,y3 i x4,y4 są takie same, to rysowana jest cała elipsa. Jeśli się różnią (a właściwie jeśli wyznaczają ze środkiem prostokąta różne linie), to zostanie narysowany fragment elipsy - łuk. Linia brzegowa w kolorze Canvas->Pen->Color.

 


   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