FORMULARZ
 

Menu


Tworzenie okien o dowolnych kształtach.


  Borland C++ Builder podczas uruchamiania tworzy domyślnie okno formularza o prostokątnym kształcie. Jednak nie jesteśmy skazani tylko na okna w takim kształcie. W prosty sposób można utworzyć formularz w kształcie np. Elipsy.


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  HRGN rgn;
  rgn = CreateEllipticRgn(100, 100, 400, 300);
  SetWindowRgn(Handle, rgn, true);
  DeleteObject(rgn);
}
//--------------------------------

 

Można również w podobny sposób utworzyć formularz w kształcie prostokąta o zaokrąglonych rogach.


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  HRGN rgn;
  rgn = CreateRoundRectRgn(50, 10, 200, 400, 20, 30);
  SetWindowRgn(Handle, rgn, true);
  DeleteObject(rgn);
}
//--------------------------------

 

...lub też w kształcie prostokąta o ostrych rogach.


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  HRGN rgn;
  rgn = CreateRectRgn(50, 10, 200, 400);
  SetWindowRgn(Handle, rgn, true);
  DeleteObject(rgn);
}
//--------------------------------

 

Jednak w podanych przykładach tworzone są tylko bardzo proste kształty, można jednak tworzyć również kombinacje podanych przykładów tworząc bardziej złożone kształty. W tym celu należy posłużyć się funkcją 'API' - 'CombineRgn()'.


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  HRGN rgn1, rgn2, rgn3, rgn4;
  rgn1 = CreateEllipticRgn(100, 100, 300, 300);
  rgn2 = CreateRoundRectRgn(200, 200, 600, 600, 20, 20);
  rgn3 = CreateRectRgn(300, 50, 400, 350);
  rgn4 = CreateRectRgn(Left, Top, Left+Width, Top+Height);

  CombineRgn(rgn4, rgn1, rgn2, RGN_OR);
  CombineRgn(rgn4, rgn4, rgn3, RGN_OR);

  SetWindowRgn(Handle, rgn4, true);

  DeleteObject(rgn1);
  DeleteObject(rgn2);
  DeleteObject(rgn3);
  DeleteObject(rgn4);
}
//--------------------------------

 

W podanym przykładzie formularz został stworzony z trzech kształtów: elipsy, prostokąta oraz prostokąta z zaokrąglonymi kształtami. Tworzenie dwóch kształtów jest znacznie prostsze.


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  HRGN rgn1, rgn2, rgn3;
  rgn1 = CreateEllipticRgn(100, 100, 300, 300);
  rgn2 = CreateEllipticRgn(50, 200, 600, 500);
  rgn3 = CreateRectRgn(Left, Top, Left+Width, Top+Height);

  CombineRgn(rgn3, rgn1, rgn2, RGN_OR);

  SetWindowRgn(Handle, rgn3, true);

  DeleteObject(rgn1);
  DeleteObject(rgn2);
  DeleteObject(rgn3);
}
//--------------------------------

 

Formularza o taki kształtach nie można przesuwać, ponieważ nie posiada belki tytułowej, dlatego trzeba dołączyć procedurę przesuwania formularza. Opis takiej procedury został umieszczony w kolejnej poradzie.

 

...powrót do menu. 

 

Przesuwanie formularza po uchwyceniu go w dowolnym miejscu - sposób 1.

 

  Przesuwanie formularza po uchwyceniu go w dowolnym miejscu opiera się na 'przekonaniu' formularza, że uchwyciliśmy belkę tytułową. Najpierw przechodzimy do pliku nagłówkowego (np. Unit1.h) i umieszczamy w sekcjach 'private:' i 'public:' deklarację funkcji obsługującej komunikat 'WM_NCHITTEST' oraz mapę komunikatu.


// Plik nagłówkowy np. Unit1.h.
//--------------------------------
private:
  void __fastcall Przesun(TMessage &Msg);

public:
  BEGIN_MESSAGE_MAP
  MESSAGE_HANDLER(WM_NCHITTEST, TMessage, Przesun)
  END_MESSAGE_MAP(TForm)
//--------------------------------

 

Następnie przechodzimy do pliku źródłowego (np. Unit1.cpp) i dodajemy funkcję obsługującą komunikat.


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Przesun(TMessage &Msg)
{
  TForm::Dispatch(&Msg);
    if (Msg.Result == HTCLIENT)
         Msg.Result = HTCAPTION;
}
//--------------------------------

 

...no i tyle wystarczy.

...powrót do menu. 


Przesuwanie formularza po uchwyceniu go w dowolnym miejscu - sposób 2.

  Drugi sposób jest znacznie prostszy, pozwala również na przesuwanie formularza po uchwyceniu obiektów znajdujących się na nim. Najpierw utwórzmy nowy projekt i umieśćmy na nim np. komponent Image, który posłuży nam do zobrazowania przykładu przesuwania formularza po uchwyceniu obiektu znajdującego się na nim. Następnie przejdźmy do pliku nagłówkowego (np. Unit1.h) i w sekcji private umieśćmy definicję funkcji 'void __fastcall Przesun(TMouseButton Button);'.


// Plik nagłówkowy np. Unit1.h.
//--------------------------------
private:
void __fastcall Przesun(TMouseButton Button);
//--------------------------------

 

Teraz przejdźmy do pliku źródłowego (np. Unit1.cpp) i dodajmy obsługę nowo utworzonej funkcji.


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Przesun(TMouseButton Button)
{
if (Button == mbLeft)
   {

     ReleaseCapture();
     SendMessage(Handle, WM_LBUTTONUP, 0, 0);
     SendMessage(Handle, WM_NCLBUTTONDOWN, HTCAPTION, 0);
   }
}
//--------------------------------

 

Następnie w zdarzeniu formularza - 'OnMouseDown' i w zdarzeniu Image - 'OnMouseDown ' wywołajmy funkcję 'Przesun...'.


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Image1MouseDown(TObject *Sender, TMouseButton Button,

        TShiftState Shift, int X, int Y)
{
Przesun(Button);
}
//--------------------------------
void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,

        TShiftState Shift, int X, int Y)
{
Przesun(Button);
}
//--------------------------------

 

...i w zasadzie to wszystko, tak jak to pokazano w powyższym przykładzie funkcję 'Przesun(TMouseButton Button) ' można wywołać dla każdego komponentu wyposażonego w zdarzenie 'OnMouseDown'.


...powrót do menu. 


Dynamiczne tworzenie okna formularza.

  Dynamiczne tworzenie okna formularza polega np. na wywoływaniu obiektu 'Form2' z poziomu obiektu 'Form1'. Dynamiczne - ponieważ obiekt nie jest tworzony w czasie uruchamiania programu, lecz wtedy gdy jest potrzebny i jest usuwany gdy już go nie potrzebujemy. Co to daje? No cóż oszczędza pamięć RAM, jeżeli obiekt nie jest tworzony w czasie uruchamiania programu to nie trzeba mu przydzielać pamięci, natomiast gdy zostanie już utworzony i następnie zamknięty to zwolni pamięć którą zajmował. W prezentowany przykładzie utworzymy dwa formularze - 'Form1' i 'Form2'. 'Form1' będzie oknem głównym programu, 'Form2' natomiast będzie tworzony dynamicznie. Na obydwu formularzach umieszczamy przyciski 'Button1'. Po kliknięciu na przycisk znajdujący się na 'Form1', zostanie utworzony 'Form2' a 'Form1' zostanie ukryty. Natomiast po kliknięciu na przycisk znajdujący się na 'Form2' - 'Form1' zostanie przywołany a 'Form2' - usunięty z pamięci.
  W tym celu w menu programu Borland C++ Builder, w Project|Options (sugeruję się menu BCB 4, w innych wersjach Options może być gdzie indziej), odnajdujemy zakładkę Froms i w oknie Auto-create forms: odnajdujemy wpis Form2, a nastęnie przenosimy go do okna: Available forms:.

Project|Options

 

Następnie przechodzimy do pliku źródłowego Unit1.cpp i w sekcji include dodajemy wpis: #include "Unit2.h".

// Plik źródłowy Unit1.cpp
//--------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
#include "Unit2.h"
//dodawany wpis Unit2.h
//--------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//--------------------------------


Tak samo postępujemy w pliku źródłowym Unit2.cpp z tą jednak różnicą, że tutej oczywiście dodajemy wpis: #include "Unit1.h".

// Plik źródłowy Unit2.cpp
//--------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit2.h"
#include "Unit1.h"
//dodawany wpis Unit1.h
//--------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm2 *Form2;
//--------------------------------


Następnie powracamy do pliku źródłowego Unit1.cpp i w zdarzeniu przycisku 'Button1' - 'OnClick' umieszczamy procedurę tworzenia obiektu 'Form2'.

// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TForm2 *frm = new TForm2(this);   //Tworzenie formularza Form2 poprzez zdefiniowanie nowego obiektu frm.
frm->Show();   // wywołanie nowo utworzonego formularza.
Form1->Hide();   //ukrycie formularza głównego Form1
}
//--------------------------------


Teraz przechodzimy do pliku źródłowego Unit2.cpp i podobnie jak poprzednio w zdarzeniu przycisku 'Button1' - 'OnClick' (nazwa ta same 'Button1' lecz jest to oczywiście obiekt znajdujący się na 'Form2'), umieszczamy procedurę przywołania okna formularza głównego 'Form1' oraz usunięcia z pamięci obiektu 'Form2'.

// Plik źródłowy Unit2.cpp
//--------------------------------
void __fastcall TForm2::Button1Click(TObject *Sender)
{
Form1->Show();   //wywołanie okna formularza głównego - Form1
Form2->Free();   //usunięcie obiektu Form2 i zwolnienie zajmowanej przez niego pamięci.
}
//--------------------------------

...powrót do menu. 


Ukrywanie i wyświetlanie formularza.

  W celu ukrycia formularza możemy posłużyć się metodą 'Hide' lub po prostu uczynić formularz niewidocznym poprzez ustawienie jego właściwości 'Visible' na false. Natomiast, żeby wyświetlić formularz można posłużyć się metodą 'Show' lub po prostu uczynić formularz widocznym poprzez ustawienie jego właściwości 'Visible' na true.
  W prezentowanym przykładzie sprawimy, żeby po kliknięciu przycisku 'Button1' na formularzu 'Form1' wywołać formularz 'Form2' i ukryć formularz 'Form1'. W tym celu tworzymy dwa formularze 'Form1' i 'Form2'. 'Form1' będzie formularzem głównym więc w Object Inspector w zakładce Properties jego właściwość 'Visible' powinna być ustawiona na true, natomiast właściwość 'Visible' dla formularza 'Form2' powinna być ustawiona na false (gdyby właściwość 'Visible' była ustawiona dla obydwu formularzy na true, to po uruchomieniu programu obydwa formularze byłyby widoczne).
Trzeba jeszcze "powiadomić" obydwa formularze o własnym istnieniu dlatego w plikach źródłowych Unit1.cpp i Unit2.cpp w sekcji include umieszczamy wpisy: #include "Unit2.h" i w drugim pliku: #include "Unit1.h".

// Plik źródłowy Unit1.cpp
//--------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
#include "Unit2.h"
  //dodany wpis Unit2.h
//--------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

TForm1 *Form1;
//--------------------------------


// Plik źródłowy Unit2.cpp
//--------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit2.h"
#include "Unit1.h"
  //dodany wpis Unit1.h
//--------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

TForm2 *Form2;
//--------------------------------


Jeżeli nie umieścilibyśmy wpisów do sekcji include to formularze nic o sobie by nie wiedziały i przy próbie skompilowania wyskoczyłby komunikat o błędzie: Undefined symbol 'Form2'. Następnie przechodzimy do pliku źródłowego Unit1.cpp i w zdarzeniu OnClick umieszczamy metody 'Show' i 'Hide'.

// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
   Form2->Show();
   Form1->Hide();
}
//--------------------------------


Następnie przechodzimy do pliku źródłowego Unit2.cpp i podobnie jak poprzednio z tym, że dla przycisku 'Button1' znajdującego się na formularzu 'Form2' w zdarzeniu 'OnClick' umieszczamy te same metody co poprzednio tylko, że na odwrót.

// Plik źródłowy Unit2.cpp
//--------------------------------
void __fastcall TForm2::Button1Click(TObject *Sender)
{
   Form1->Show();
   Form2->Hide();
}
//--------------------------------


No i to wszystko. Teraz formularze będą otwierały i zamykały się na przemian.

...powrót do menu. 

 

Rysowanie na pasku tytułowym formularza.

 

    Jedyne co możemy zmienić na pasku tytułowym formularza - TForm to jest właściwość Caption i ikony do minimalizacji, maksymalizacji i zamykania. Istnieje jednak pewien sposób by umieścić tam własny tekst lub rysunek. Służy do tego funkcja API - GetWindowDC i należy ją wywoływać w zdarzeniu OnPaint formularza:

// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
 TCanvas *CCanv = new TCanvas;
 CCanv->Handle = GetWindowDC(Handle);
 CCanv->Brush->Style = bsClear; //<-- ustawia przeźroczyste tło pod tekstem.   
 CCanv->Font->Style = CCanv->Font->Style << fsBold; //<-- ustawia pogrubioną czcionkę.
 CCanv->Font->Size = 12; //<-- ustawia rozmiar czcionki.
 CCanv->TextOut(10, 2, "Przykładowy tekst"); //<-- rysuje tekst na obiekcie CCanv->TextOut(X, Y, AnsiString).
 ReleaseDC(Handle, CCanv->Handle); //<-- przenosi obiekt CCanv na formularz.
 delete CCanv; //<-- usuwa obiekt CCanv z pamięci.
}
//--------------------------------

 

Trzeba wiedzieć, że funkcja GetWindowDC pobiera uchwyt nie tylko do paska tytułowego, ale do całego formularza dlatego gdybym zrobił tak:


 Pierwszy parametr X   Drugi parametr Y
          ↓                  ↓        

CCanv->TextOut(10, 30, "Przykładowy tekst"); //<-- zmieniłem parametr 2 na 30.

 

...to tekst został by narysowany na formularzu poniżej paska tytułowego, ponieważ pierwszy parametr określa odległość o lewego brzegu formularza w poziomie, natomiast parametr drugi określa odległość od góry formularza w pionie.
Żeby umieścić na formularzu bitmapę, trzeba najpierw przygotować sobie odpowiedni plik w formacie BMP a następnie należy postąpić tak:


// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
 TCanvas *CCanv = new TCanvas;
 Graphics::TBitmap *bmp = new Graphics::TBitmap();
 bmp->LoadFromFile("nazwa_pliku.bmp");

 CCanv->Handle = GetWindowDC(Handle);
 CCanv->Draw(0, 0, bmp);
 ReleaseDC(Handle, CCanv->Handle);
 delete CCanv;
 delete bmp;
}
//--------------------------------

 

...powrót do menu. 

 

Przechwyceniu komunikatu o przesunięciu formularza.

 

    Tworząc pewien program stanąłem przed problemem jak sprawdzić w którym momencie formularz jest przesuwany. Spróbujcie zainicjować wykonanie dowolnej instrukcji ale tylko wtedy gdy formularz np. Form1 jest przesuwany, a zobaczycie że nie istnieje żadne zdarzenie, które dałoby zadowalające efekty i OnMouseMove nie przyda się na wiele.
Załóżmy, że potrzebujemy umieścić kod uniemożliwiający przesunięcie formularza poza krawędzie pulpitu. Żeby zrealizować to zadanie przechodzimy do pliku nagłówkowego (np. Unit1.h) i w sekcji private definiujemy funkcję SetMoveWindow, przy czym nazwa funkcji jest dowolna. W sekcji public umieszczamy mapę komunikatu:

// Plik nagłówkowy np. Unit1.h
//--------------------------------
private:
        void __fastcall
SetMoveWindow(TMessage &Msg);

public:
        __fastcall TForm1
(TComponent* Owner);

BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_SYSCOMMAND, TMessage, SetMoveWindow);
END_MESSAGE_MAP(TForm)
//--------------------------------

 

Następnie przechodzimy do pliku źródłowego (np. Unit1.cpp) i definiujemy zadeklarowaną funkcję SetMoveWindow:


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::SetMoveWindow(TMessage &Msg)
{
 TForm::Dispatch(&Msg);
 if(Msg.WParam == SC_MOVE + HTCAPTION)
 {
  ; // tutaj można umieścić dowolne instrukcje, które zostaną wykonane przy przesunięciu formularza.
 }
}
//--------------------------------

 

Tak zdefiniowana funkcja przechwytuje już komunikat przesunięcia formularza Form1 jednak nie wykonuje żadnych instrukcji ponieważ ich tam nie ma. Umieszczę tam kod uniemożliwiający przesunięcie formularza Form1 poza krawędzie pulpitu:


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::SetMoveWindow(TMessage &Msg)
{
 TForm::Dispatch(&Msg);
 if(Msg.WParam == SC_MOVE + HTCAPTION)
 {
  if(Left > Screen->Width - Width - 2) Left = Screen->Width - Width - 2;
  if(Left < 2) Left = 2;
  if(Top > Screen->Height - Height - 50) Top = Screen->Height - Height - 50;
  if(Top < 50) Top = 30;
 }
}
//--------------------------------

 

...powrót do menu. 

 

Jak uczynić formularz przeźroczystym?

 

 Żeby uczynić formularz przeźroczystym należy utworzyć metodę CreateParams. Przedstawiona tutaj metoda sprawdzi się tylko w nieruchomych formach, ponieważ po przesunięciu formularza po pulpicie przeźroczystość zostanie utracona:

// Plik nagłówkowy np Unit1.h
//--------------------------------
private:
       void __fastcall CreateParams(TCreateParams &Params);

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


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::CreateParams(TCreateParams &Params) 

 TForm::CreateParams(Params); 
 Params.ExStyle = Params.ExStyle | WS_EX_TRANSPARENT; 
}
//--------------------------------

 

...powrót do menu. 

 

Umieszczanie grafiki na pasku tytułowym.

 

 Żeby umieścić bitmapę na pasku tytułowym formularza należy posłużyć się funkcją API - GetWindowDC:


// Plik nagłówkowy np Unit1.h
//--------------------------------
private:
       TCanvas *MyCanvas;
       Graphics::TBitmap *bmp;

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


// Plik źródłowy np. Unit1.cpp
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
 MyCanvas = new TCanvas;
 bmp = new Graphics::TBitmap;
 bmp->LoadFromFile("bitmapa.bmp");
}
//--------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
 MyCanvas->Handle = GetWindowDC(Handle);
 MyCanvas->Draw(2, 2, bmp);
 ReleaseDC(Handle, MyCanvas->Handle);
}
//--------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
 delete MyCanvas;
 delete bmp;
}
//--------------------------------

 

...powrót do menu. 

 

Blokowanie zmiany rozmiaru, przenoszenia i zamykania formularza.

 

  Pokażę kilka funkcji umożliwiających ograniczenie pewnych funkcji formularza. Posłużę się funkcją API GetSystemMenu, która zwraca uchwyt do kopii menu systemowego. Jako pierwszy argument funkcja pobiera uchwyt do menu systemowego, a jako drugi wartość typu bool, jeżeli wartość ta jest ustawiona na FALSE, to włącza się blokowanie funkcji formularza w przeciwnym razie zablokowane funkcje zostają odblokowane.
Przykład pierwszy blokuje możliwość zmiany rozmiaru formularza:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  HMENU hMenu = GetSystemMenu(Handle, FALSE);
  DeleteMenu(hMenu, SC_SIZE, MF_BYCOMMAND);
}
//--------------------------------

 

Przykład drugi blokuje możliwość przenoszenia formularza:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  HMENU hMenu = GetSystemMenu(Handle, FALSE);
  DeleteMenu(hMenu, SC_MOVE, MF_BYCOMMAND);
}
//--------------------------------

 

Przykład trzeci blokuje możliwość zamknięcia formularza:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  HMENU hMenu = GetSystemMenu(Handle, FALSE);
  DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
}
//--------------------------------

 

Istnieje jeszcze możliwość blokowania 'Przywracania', 'Minimalizacji' i 'Maksymalizacji' formularza, jednak nie działa to w środowisku Windows XP.

Przywracanie funkcji menu systemowego przebiega podobnie, trzeba jedynie zmienić wartość FALSE na TRUE:


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  HMENU hMenu = GetSystemMenu(Handle, TRUE);
  DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
}
//--------------------------------

 

Jeżeli chodzi o blokowanie możliwości zamknięcia formularza, to istnieją jeszcze dwa sposoby, które działają podobnie. W pierwszym przypadku należy posłużyć się zdarzeniem OnClose, w drugim OnCloseQuery dla formularza:

Sposób pierwszy:


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
  Action = caNone;
}
//--------------------------------

 

Sposób drugi:


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormCloseQuery(TObject *Sender, bool &CanClose)
{
  CanClose = false;
}
//--------------------------------

 

Tak zablokowany formularz nie pozwoli się zamknąć nawet kombinacją klawiszy Alt+F4, można przywrócić opcję zamykania formularza poprzez zmianę tych parametrów na: Actrion = caFree; i CanClose = true;. Ustawienia te muszą być wprowadzone w tych samych zdarzeniach, a oto przykład:


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
  if(Application->MessageBox("Zamknąć program?", "Zamykanie programu", MB_YESNO | MB_ICONQUESTION) == ID_YES)
   Action = caFree;
  else
   Action = caNone;
}
//--------------------------------

 

Podobnie będzie wyglądało to w zdarzeniu OnCloseQuery, należy tylko posłużyć się funkcją CanClose. Na zakończenie jeszcze jedna ciekawostka, otóż można zamiast zamknięcia formularza wymusić na nim minimalizację, tak przynajmniej jest to opisane w pliku pomocy BCB, jednak nie potwierdza się to w praktyce, w środowisku Windows XP to nie zadziałało:


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
 Action = caMinimize;
}
//--------------------------------

 

Można oczywiście zminimalizować okno, zamiast je zamykać, jednak trzeba posłużyć się inną funkcją:


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
  if(Application->MessageBox("Zamknąć program?", "Zamykanie programu", MB_YESNO | MB_ICONQUESTION) == ID_YES)
   Action = caFree;
  else
  {
   Action = caNone;
   Application->Minimize();
  }
}
//--------------------------------

 

Program zawsze można zamknąć używając funkcji Terminate:


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Application->Terminate();
}
//--------------------------------

 

Ta funkcja powoduje jednak zamknięcie całej aplikacji, a nie wybranego okna.

 

...powrót do menu. 

 

Tworzenie formy o dowolnych kształtach poprzez nałożenie maski w formie bitmapy.

 

Przedstawiona tu porada bazuje na poradzie tworzenie okien o dowolnych kształtach i cała sztuka polega na traktowaniu pojedynczego piksela jako regionu, a potem połączenie wszystkich regionów w jeden, żeby zastosować tą poradę należy umieścić na formularzu obiekt Image1 i wczytać do niego bitampę, która będzie maską dla formularza. Formularz zostanie obcięty w oparciu o kolor czarny, ale można stosować inne kolory poprzez modyfikowanie zmiennej kolor:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
 HRGN wynikRgn = CreateRectRgn(0, 0, 0, 0);

 for(int x = 0; x < Width; x++)
 {
  for(int y = 0; y < Height; y++)
  {
   UINT kolor_1 = Image1->Picture->Bitmap->Canvas->Pixels[x][y];

   if(kolor_1 != 0)
   {
    HRGN rgn = CreateRectRgn(x, y, x + 1, y + 1);
    CombineRgn(wynikRgn, rgn, wynikRgn, RGN_OR);
    DeleteObject(rgn);
   }
  }
 }
 SetWindowRgn(Handle, wynikRgn, true);
}
//--------------------------------

 

Opracował: Artur Wojnar.

 

Przedstawiona metoda ma dość istotny mankament, im większa grafika, tym dłużej trwa obcinanie formularza, dlatego pozwoliłem sobie nieco zmodyfikować ten kod, w oparciu o kod kolegi, poprzez zastosowanie funkcji ScanLine:


// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
 HRGN wynikRgn = CreateRectRgn(0, 0, 0, 0);

 Image1->Picture->Bitmap->PixelFormat = pf32bit;
 for(int i = 0; i < Height; i++)
 {
  RGBQUAD *Pixel = (RGBQUAD *) Image1->Picture->Bitmap->ScanLine[i];

  for(int j = 0; j < Width; j++, Pixel++)
  {
   int kolor = Pixel->rgbBlue + Pixel->rgbGreen + Pixel->rgbRed;

   if(kolor != 0)
   {
    HRGN rgn = CreateRectRgn(j, i, j + 1, i + 1);
    CombineRgn(wynikRgn, rgn, wynikRgn, RGN_OR);
    DeleteObject(rgn);
   }
  }
 }

 SetWindowRgn(Handle, wynikRgn, true);
}
//--------------------------------

 

Niestety i to tylko w niewielkim stopniu skraca czas obcinania formularza, wpadłem na pomysł, żeby podzielić maskę (bitmapę) na cztery części i każdą część obcinać oddzielnie, jednak obcinanie nawet czterech części w czterech różnych pętlach niczego nie zmieni, ponieważ każda kolejna pętla będzie zaczynała obieg dopiero gdy poprzednia zakończy swój, więc żeby rozwiązać ten problem należy utworzyć cztery wątki, i w każdej umieścić pętlę, oczywiście znacznie lepszym pomysłem jest stworzenie funkcji tworzącej regiony i wywoływanie jej w każdym wątku z innymi parametrami, w ten sposób uniknie się powielania kodu. Oczywiście gdy każdy wątek zakończy obcinanie swojego regionu należy to wszystko jakoś połączyć w całość, początkowo myślałem o zastosowaniu obiektu Timer, jednak lepszym rozwiązaniem jest stworzenie piątego wątku i zrealizowanie tego zadania w nim, a oto kompletny kod:


// Plik nagłówkowy np Unit1.h
//--------------------------------
private:

public:
       int
W_ID1, W_ID2, W_ID3, W_ID4, W_ID5;
       unsigned int
W_PD;
       bool Obcinaj(int Xpp, int h, int w, Graphics::TBitmap *BMP, void *hWnd, HRGN hRgn);
       HRGN hRgn1, hRgn2, hRgn3, hRgn4, hRgn5;
       bool rg1, rg2, rg3, rg4;

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


// Plik źródłowy np. Unit1.cpp
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 hRgn1 = CreateRectRgn(0, 0, 0, 0);
 hRgn2 = CreateRectRgn(0, 0, 0, 0);
 hRgn3 = CreateRectRgn(0, 0, 0, 0);
 hRgn4 = CreateRectRgn(0, 0, 0, 0);
 hRgn5 = CreateRectRgn(0, 0, Width, Height);
 rg1 = rg2 = rg3 = rg4 = false;
}
//--------------------------------
bool TForm1::Obcinaj(int Xpp, int h, int w, Graphics::TBitmap *BMP, void *hWnd, HRGN hRgn)
{
 BMP->PixelFormat = pf32bit;
 int i, j;
 for(i = Xpp; i < h; i++)
 {
  RGBQUAD *Pixel = (RGBQUAD *)BMP->ScanLine[i];

  for(j = 0; j < w; j++, Pixel++)
  {
   int kolor = Pixel->rgbBlue + Pixel->rgbGreen + Pixel->rgbRed;

   if(kolor != 0)
   {
    HRGN rgn = CreateRectRgn(j, i, j + 1, i + 1);
    CombineRgn(hRgn, rgn, hRgn, RGN_OR);
    DeleteObject(rgn);
   }
  }
 }
 return true;
}
//--------------------------------
int __fastcall Weft1(Pointer P)
{
 Form1->rg1 = Form1->Obcinaj(0, Form1->Height/4, Form1->Width, Form1->Image1->Picture->Bitmap,

              Form1->Handle, Form1->hRgn1);
}
//--------
int __fastcall Weft2(Pointer p)
{
 Form1->rg2 = Form1->Obcinaj(Form1->Height/4, Form1->Height/2, Form1->Width,

              Form1->Image1->Picture->Bitmap, Form1->Handle, Form1->hRgn2);
}
//----------
int __fastcall Weft3(Pointer P)
{
 Form1->rg3 = Form1->Obcinaj(Form1->Height/2, Form1->Height - Form1->Height/4, Form1->Width,

              Form1->Image1->Picture->Bitmap, Form1->Handle, Form1->hRgn3);
}
//--------
int __fastcall Weft4(Pointer p)
{
 Form1->rg4 = Form1->Obcinaj(Form1->Height - Form1->Height/4, Form1->Height, Form1->Width,

              Form1->Image1->Picture->Bitmap, Form1->Handle, Form1->hRgn4);
}
//--------------------------------
int __fastcall Zegar(Pointer p)
{
 for(int i = 0;i < 1;)
 {
  if(Form1->rg1 && Form1->rg2 && Form1->rg3 && Form1->rg4)
  {
   CombineRgn(Form1->hRgn5, Form1->hRgn1, Form1->hRgn2, RGN_OR);
   CombineRgn(Form1->hRgn5, Form1->hRgn5, Form1->hRgn3, RGN_OR);
   CombineRgn(Form1->hRgn5, Form1->hRgn5, Form1->hRgn4, RGN_OR);
   SetWindowRgn(Form1->Handle, Form1->hRgn5, true);
   SuspendThread((Pointer)Form1->W_ID1);
   SuspendThread((Pointer)Form1->W_ID2);
   SuspendThread((Pointer)Form1->W_ID3);
   SuspendThread((Pointer)Form1->W_ID4);
   SuspendThread((Pointer)Form1->W_ID5);
   i = 2;
  }
 }
}
//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
 W_ID1 = BeginThread(NULL, 0, Weft1, this, 0, W_PD);
 W_ID2 = BeginThread(NULL, 0, Weft2, this, 0, W_PD);
 W_ID3 = BeginThread(NULL, 0, Weft3, this, 0, W_PD);
 W_ID4 = BeginThread(NULL, 0, Weft4, this, 0, W_PD);
 W_ID5 = BeginThread(NULL, 0, Zegar, this, 0, W_PD);
}
//--------------------------------

 

Jeśli chodzi o tą poradę to już nic więcej nie wymyślę, żeby skrócić czas obcinania należy zastosować jak największą liczbę wątków dzieląc maskę (bitmapę) na jak największą liczbę części, na zakończenie przykład kombinacji regionów dla dla 8 części:


HRGN hRgn1, hRgn2, hRgn3, hRgn4, hRgn5, hRgn6, hRgn7, hRgn8, hRgn9;

hRgn1 = CreateRectRgn(0, 0, 0, 0);
hRgn2 = CreateRectRgn(0, 0, 0, 0);
hRgn3 = CreateRectRgn(0, 0, 0, 0);
hRgn4 = CreateRectRgn(0, 0, 0, 0);
hRgn5 = CreateRectRgn(0, 0, 0, 0);
hRgn6 = CreateRectRgn(0, 0, 0, 0);
hRgn7 = CreateRectRgn(0, 0, 0, 0);
hRgn8 = CreateRectRgn(0, 0, 0, 0);
hRgn9 = CreateRectRgn(0, 0, Width, Height);

CombineRgn(Form1->hRgn9, Form1->hRgn1, Form1->hRgn2, RGN_OR);
CombineRgn(Form1->hRgn9, Form1->hRgn9, Form1->hRgn3, RGN_OR);
CombineRgn(Form1->hRgn9, Form1->hRgn9, Form1->hRgn4, RGN_OR);
CombineRgn(Form1->hRgn9, Form1->hRgn9, Form1->hRgn5, RGN_OR);
CombineRgn(Form1->hRgn9, Form1->hRgn9, Form1->hRgn6, RGN_OR);
CombineRgn(Form1->hRgn9, Form1->hRgn9, Form1->hRgn7, RGN_OR);
CombineRgn(Form1->hRgn9, Form1->hRgn9, Form1->hRgn8, RGN_OR);

SetWindowRgn(Form1->Handle, Form1->hRgn9, true);

 

Jeśli ktoś zna inny sposób na zrealizowanie tego zadania, to chętnie go poznam.

...powrót do menu. 

 

Tworzenie formy o stałej niezmiennej szerokości.

 

W tej poradzie pokaże jak za pomocą funkcji przechwytującej komunikaty stworzyć formularz, który nawet przy maksymalizacji zachowa stałą szerokość, w takim formularzu można zmieniać rozmiar, ale zmieniała się będzie tylko długość, szerokość zawsze pozostanie taka sama. W taki sposób skonstruowany jest formularz środowiska BCB, to okno u góry ekranu na którym znajdują się komponenty:

// Plik nagłówkowy np Unit1.h
//--------------------------------
private:
        virtual void __fastcall WndProc(TMessage &Msg);

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


// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::WndProc(TMessage &Msg)
{
 switch(Msg.Msg)
 {
  case WM_GETMINMAXINFO:
                        LPMINMAXINFO lpMMI;
                        lpMMI = reinterpret_cast<LPMINMAXINFO>(Msg.LParam);
                        lpMMI->ptMaxTrackSize.y = 110;
                        break;
 }
TForm::WndProc(Msg);
}
//--------------------------------

 

Komunikat przechwytuje informację o zmianie rozmiaru formularza i zawsze ustawia jego szerokość na 110 pikseli.

...powrót do menu. 

 

Dynamiczne tworzenie formularzy.

 

Niniejsza porada stanowi uzupełnienie porady: dynamiczne tworzenie okna formularza.
Sposób dynamicznego tworzenia obiektów jest pewnie wszystkim dobrze znany, w tym także sposób na tworzenie formularza, dlatego chcę tutaj przedstawić trochę nietypowy sposób, tzn. ten sposób jest jak najbardziej typowy dla C++ Builder, tylko nie wszyscy może o nim wiedzą. Prosty sposób na dynamiczne utworzenie formularza jest taki:

 

// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::Form1Show(TObject *Sender)
{
 TForm *MyForm = new TForm(Application);
 MyForm->Visible = true;
}
//--------------------------------

 

Proste! W ten sposób utworzymy pusty formularz, taki jaki się tworzy przy uruchamianiu nowego projektu w BCB. Istnieje możliwość dynamicznego utworzenia formularza, będącego kopią już stworzonego formularza, np. gdybyśmy chcieli utworzyć klon formularza Form1:


// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::Form1Show(TObject *Sender)
{
 TForm1 *MyForm = new TForm1(Application);
 MyForm->Show();
}
//--------------------------------

 

W ten sposób powstanie nowy formularz będący dokładną kopią formularza Form jeden ze wszystkimi obiektami, zmiennymi i funkcjami jakie znajdują się na Form1, czyli na MyForm pojawią się dokładnie te same obiekty, odwołanie do tych obiektów odbywa się dokładnie tak samo jak do obiektów na Form1:


// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::Form1Show(TObject *Sender)
{
 TForm1 *MyForm = new TForm1(Application);
 MyForm->Show();
 MyForm1->Edit1->Text = "Jakiś tekst";
}
//--------------------------------

 

Niejakim problemem może być próba dynamicznego utworzenia formularza o takiej samej nazwie powtórnie, czyli raz utworzyliśmy formularz o nazwie MyForm i próbujemy  utworzyć nowy egzemplarz formularza o takiej samej nazwie. W takiej sytuacji program się posypie, ponieważ nie można utworzyć dwóch formularzy o tej samej nazwie. Rozwiązaniem tego problemu jest oczywiście usunięcie formularza przed ponownym jego utworzeniem. Jeżeli tworzymy formularz w oparciu o klasę TForm (nie np. TForm1) wewnątrz jakiegoś zdarzenia to można go usunąć tylko wewnątrz tego zdarzenia, ponieważ poza tym zdarzeniem wydaje się on nie istnieć:


// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::Form1Show(TObject *Sender)
{
 TForm *MyForm = new TForm(Application);
 MyForm->ShowModal();
 delete MyForm;
}
//--------------------------------

 

Rozwiązaniem może być zadeklarowanie formularza w sekcji private lub public pliku nagłówkowego, wtedy będzie możliwe zniszczenie tego obiektu w każdym zdarzeniu:


// Plik nagłówkowy np Unit1.h
//--------------------------------
private:
       
TForm *MyForm;
//--------------------------------


// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::Form1Show(TObject *Sender)
{
 MyForm = new TForm(Application);
 MyForm->Show();
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 delete MyForm;
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 MyForm = new TForm(Application);
 MyForm->Show();
}
//--------------------------------

 

Ważnym jest, żeby pamiętać o zniszczeniu formularze, przed ponownym utworzeniem formularza o tej samej nazwie. W sytuacji gdy tworzymy formularz w oparciu o już istniejący (np. Form1) postępujemy tak samo, jednak istnieje sposób na to żeby formularz sam się zniszczył przy zamykaniu. Tworząc dynamicznie formularz w oparciu o istniejący już formularz, np. TForm1 należy w zdarzeniu OnClose dla formularza Form1 dodać kod niszczący:


// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm5::FormClose(TObject *Sender, TCloseAction &Action)
{
 Action = caFree;
}
//--------------------------------

 

W ten sposób każdy dynamicznie utworzony obiekt w oparciu o klasę TForm1 będzie również posiadał kod niszczący go przy zamykaniu, wiąże się to oczywiście z tym, że po zamknięciu formularza, w tym również Form1 zostanie on zniszczony i żeby go ponownie uruchomić należy go ponownie utworzyć, nie wystarczy zwykłe Form1->Show(). Dlatego stosowanie tej metody w odniesieniu do formularza głównego ma sens ponieważ zamknięcie tego formularza wiąże się z zamknięciem całej aplikacji.
W przypadku innych formularzy wchodzących w skład aplikacji, ma to również praktyczne zastosowanie, ale trzeba wyłączyć tworzenie takich formularzy przy uruchamianiu aplikacji, daje to dodatkowy plus, ponieważ formularz, który będzie w ten sposób tworzony nie zajmuje miejsca w pamięci dopóki nie zostanie utworzony, a po zniszczeniu zwalnia pamięć.
Jeżeli w skład aplikacji wchodzą dwa formularze Form1 główny i Form2 dodatkowy i nie zachodzi potrzeba uruchamiania Form2 przy uruchomieniu aplikacji, to nie zachodzi również potrzeba tworzenia tego formularza przy uruchomieniu i niepotrzebnego zajmowania pamięci. Co zrobić? Tworzymy formularz Form2 ze wszystkim co ma się na nim znaleźć a następnie przechodzimy do menu Project -> Options na zakładkę Forms i przesuwamy Form2 z okienka Auto-create forms: do okienka Available forms:. Tak spreparowanego formularza nie da się już wywołać za pomocą funkcji Form2->Show(), nie da się wogóle wywołać formularza o nazwie Form2, trzeba zawsze wywoływać formularz pochodzący od klasy TForm2:


// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TForm2 *MyForm = new TForm2(Application);
 MyForm2->Show();
}
//--------------------------------

 

Należy pamiętać o umieszczeniu w zdarzeniu OnCLose dla formularza Form2 metody Action = caFree; celem zniszczenia go przy zamknięciu. Należy oczywiście pamiętać również o włączeniu do pliku źródłowego wpisu: #include Unit2.cpp - dla Unit2.

...powrót do menu. 

 

Tworzenie przeźroczystego formularza poprzez usunięcie wybranego koloru.

 

  W tej poradzie chcę pokazać sposób nie tylko na utworzenie przeźroczystego formularza, ale również na utworzenie formularza o dowolnym kształcie poprzez nałożenie np. grafiki i usunięcie z niej wybranego koloru. W celu dokładniejszego zrozumienia o co mi chodzi proponuję pobrać przykład archiwum ZIP 256 KB. Opisywany tutaj problem był już poruszany w poradzie tworzenie okien o dowolnych kształtach poprzez nałożenie mapy w formie bitmapy jednak opisany tam sposób jest mało efektywny, nieekonomiczny i daleki od oczekiwanych rezultatów, jeżeli jednak jesteś szczęśliwym posiadaczem środowiska Borland C++ Builder w wersji 6 to we właściwościach formularza powinieneś mieć dwie interesujące nas właściwości: TransparentColor i TransparentColorValue (wersji 5 nie znam, więc nie wiem czy występują tam takie właściwości). Ustawienie właściwości TransparentColor na true sprawia, ze z formularza będzie usuwany, a właściwie czyniony przeźroczystym kolor określony we właściwości TransparentColorValue. Co ciekawe przeźroczystym jest ustawiany kolor nie tylko znajdujący się bezpośrednio na formularzu, ale kolor który znajduje się na wszystkich obiektach umieszczonych na tym formularzu, czyli jeżeli umieścimy np. na formularzu obiekt Image i wczytamy do niego obrazek z czarnym tłem i ustawimy we właściwości TransparentColorValue kolor czarny, to czarne tło z Image zostanie usunięte razem z tymi częściami formularza, które to tło przykrywa. Wybranie koloru czarnego jako koloru przeźroczystego nie jest dobrym pomysłem, ponieważ ten kolor posiadają również czcionki znajdujące się na obiektach umieszczonych na formie, więc zostaną ustawione jako przeźroczyste. Proponuje z tym poeksperymentować.
Jeżeli chcemy stworzyć sobie formularz o własnym dowolnym kształcie należy jeszcze zmienić jeszcze jedną właściwość BorderStyle na bsNone, po to żeby górna belka i obramowanie formularza nie były widoczne. W ten sposób można tworzyć formularze o dowolnych kształtach lub przeźroczyste nie pisząc nawet linijki kodu. Z takim formularzem wiążą się jednak pewne kłopoty no bo jak go przesuwać jeśli nie ma belki tytułowej, ten problem da się rozwiązać stosując porady zamieszczone w tym dziale, inna sprawa to zmiana rozmiaru formularza, na to porady nie ma, ale w podanym na początku tej porady przykładzie znajduje się kompletny kod źródłowy, który rozwiązuje ten problem. Podany tam sposób wykorzystuje przypisanie polimorficzne (o czym było już na stronie) oraz komponent ApplicationEvents, ale ogólnie cały kod jest krótki prosty i zrozumiały.

 

Na zakończenie kilka praktycznych porad. Przede wszystkim jako grafiki z tłem do usunięcia można użyć dowolnego formatu graficznego o ile posiadamy odpowiednie biblioteki, czyli nie musi to być wcale bitmapa, lecz może to być również format JPEG, z tym że JPEG jest formatem stratnym, więc należy zwrócić szczególną uwagę na tło, ponieważ po przygotowaniu grafiki w tym formacie i ustawienia koloru jako tła może się okazać, że po zapisaniu zmieniły się parametry tła, i że nie jest ono jednolite, a właściwość TransparentColorValue usuwa dokładnie jeden kolor bez żadnych kolorów pośrednich. Jeżeli w trakcie tworzenia grafiki ustawię jako tło kolor clLime (dobry pomysł, kolor rzadko stosowany), wartość RGB wynosi R = 0, G = 255, B = 0, to po zapisaniu do JPEG może się okazać, że wartości RGB uległy lekkiemu przesunięciu na np. R = 1, G = 255, B = 0 (HEX = 0x0000FF01). Dla oka taka zmiana będzie niezauważalna, ale formularz nie rozpozna już tego koloru i go nie usunie ponieważ będzie miał ustawiony kolor clLime a nie 0x0000FF01, a o taką pomyłkę w przypadku formatu JPEG nie jest trudno, zresztą przy tworzeniu bitmapy w 255 kolorach również może nastąpić przesunięcie koloru. Najlepiej to widać na zamieszczonych poniżej rysunkach:

 

 

 

bitmapa, 24 bity

 

bitmapa, 8 bitów

 

JPEG, 24 bity

Na pierwszy rzut oka wyglądają identycznie, ale tylko w przypadku bitmap można usunąć zielone tło, ale już w formacie JPEG tło nie jest jednolite pomimo iż na takie wygląda i nie zostanie prawidłowo usunięte. Kolejna sprawa to ustawianie przeźroczystego tła w obiekcie Image, można je ustawiać, jednak kolor zdefiniowany jako przeźroczysty w Image nie może być tym samym kolorem który został zdefiniowany we właściwości TransparentColorValue formularza. Ciekawe efekty można uzyskać umieszczając na formularzu takie obiekty jak np. Panel, Pie czy Shape i nadając im odpowiedni kolor. Można eksperymentować z dowolnym obiektem posiadającym kolor, który można zmienić lub zdefiniować jako ten do usunięcia z formularza razem z formularzem, który przysłania.

...powrót do menu. 

Sklejanie formularzy, jednoczesne przesuwanie dwóch formularzy. - opracował: euraziel

 

Poradę tą nadesłał euraziel i pokazuje ona jak w prosty sposób przesuwać dwa formularze na raz, czyli jeżeli przesuwamy formularz pierwszy to jednocześnie przesuwa się formularz drugi zachowując położenie względem formularza pierwszego. Pozwoliłem sobie wprowadzić kilka kosmetycznych zmian do kodu, rezygnując z mapy komunikatów na rzecz klasy TWndMethod, jest to o tyle leprze rozwiązanie, że wykorzystując jedną funkcję można obsłużyć wiele komunikatów wysyłanych z/do aplikacji, w przykładzie funkcja OnFormMove będzie przechwytywała tylko komunikat o przesunięciu formularza WM_MOVING.

// Plik nagłówkowy np Unit1.h
//--------------------------------
private:
        TWndMethod Sc;
        void __fastcall
OnFormMove(TMessage& Msg);
        RECT sRect, Rect;
//--------------------------------


// Plik źródłowy Unit1.cpp
//--------------------------------
#include "Unit1.h"
#include "Unit2.h"

//--------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 Sc = WindowProc;
 WindowProc = OnFormMove;
 GetWindowRect(Handle, &Rect);
}
//--------------------------------
void __fastcall TForm1::OnFormMove(TMessage& Msg)
{
 if(Msg.Msg == WM_MOVING)
 {
  sRect = Rect;
  Rect  =* (RECT*)Msg.LParam;
  Form2->Left = Form2->Left - (sRect.left - Rect.left);
  Form2->Top  = Form2->Top  - (sRect.top  - Rect.top);
  Form1->Left = Rect.left;
  Form1->Top  = Rect.top;
 }
 Sc(Msg);
}
//--------------------------------

 

...powrót do menu. 

Upuszczanie plików na formularz.

 

Upuszczanie plików na formularz sprowadza się właściwie tylko do stworzenia obsługi komunikatu WM_DROPFILES. Na przykładzie wczytywania do obiektu Image1 pliku upuszczonego na formularz pokażę jak należy to robić. Zanim przystąpimy do tworzenia kodu umieśćmy na formularzu obiekty Image1 i Label1. Dla obiektu Image1 zmieniamy jego właściwości Stretch na true i Proportional również na true. Właściwość Stretch będzie dostosowywała automatycznie rozmiar wczytanej grafiki do rozmiaru obiektu Image, w właściwość Proportional pozwoli zachować prawidłowe proporcje obrazka. Do obiektu Label1 zostanie wczytana ścieżka dostępu do upuszczonego pliku. Obiekt Image domyślnie nie obsługuje plików *.jpg, żeby mu to umożliwić należy w pliku nagłówkowym w sekcji include dodać bibliotekę #include <JPEG.HPP>.
Przystępujemy do tworzenie kodu, w tym celu w pliku nagłówkowym w sekcji private umieszczamy następujące deklaracje:

// Plik nagłówkowy np Unit1.h
//--------------------------------
private:
       
TWndMethod Df;
        void __fastcall OnDropFiles(TMessage &Msg);
//--------------------------------

 

Tworzenie obsługi komunikatów opisywałem już wielokrotnie przy okazji innych porad, więc tym razem odpuszczę to sobie.
Przechodzimy teraz do pliku źródłowego i w konstruktorze klasy formularza umieszczamy kod łączący metodę Df z funkcją OnDropFiles, oraz włączamy akceptowanie przez formularz upuszczanych przez niego plików:


// Plik źródłowy Unit1.cpp
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 DragAcceptFiles(Handle, true); // akceptowanie upuszczanych plików
 Df = WindowProc;
 WindowProc = OnDropFiles;
}
//--------------------------------

 

Następnie tworzymy dla formularza zdarzenie OnClose i umieszczamy kod anulujący akceptowanie upuszczanych plików.

 

// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
 DragAcceptFiles(Handle, false);
}
//--------------------------------

 

Teraz tworzymy definicję funkcji OnDropFiles odpowiedzialnej za przechwytywanie komunikatu o upuszczeniu pliku na formularz i realizującej działania na tym pliku:

 

// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::OnDropFiles(TMessage &Msg)
{
 if(Msg.Msg == WM_DROPFILES)
 {
  int nFiles;
  char buffer[256];

  nFiles = DragQueryFile((HDROP)Msg.WParam, 0xFFFFFFFF, NULL, 0);
  for(int i = 0; i < nFiles; i++)
  {
   DragQueryFile((HDROP)Msg.WParam, i, buffer, 256);
   // tutaj należy umieszczać kod odpowiedzialny za wykonywanie operacji na upuszczonym pliku
   Label1->Caption = (AnsiString)buffer;
   Image1->Picture->LoadFromFile((AnsiString)buffer);
  }
  DragFinish((HDROP)Msg.WParam);
 }
 Df(Msg);
}
//--------------------------------

 

Tak skonstruowany kod będzie przechwytywał ścieżkę dostępu do upuszczonego pliku i będzie go ładował do obiektu Image1 niezależnie od tego gdzie ten obiekt zostanie upuszczony na formularzu, czyli nie tylko na obiekt Image1. Jeżeli chcemy, żeby ładowanie obrazu do Image1 było realizowane tylko w sytuacji gdy plik zostanie upuszczony na ten obiekt, należy nieco zmodyfikować kod:

 

// Plik źródłowy Unit1.cpp
//--------------------------------
void __fastcall TForm1::OnDropFiles(TMessage &Msg)
{
 POINT cPoint;
 GetCursorPos(&cPoint);
 int x = cPoint.x - Left;
 int y = cPoint.y - Top;
 TRect iRect = Image1->BoundsRect;
 TRect pRect = Rect(x, y, x + iRect.Width(), y + iRect.Height());

 if(pRect.Left >= iRect.Left && pRect.Top >= iRect.Top && pRect.Left <= iRect.Right && pRect.Top <= iRect.Bottom)
 if(Msg.Msg == WM_DROPFILES)
 {
  int nFiles;
  char buffer[256];

  nFiles = DragQueryFile((HDROP)Msg.WParam, 0xFFFFFFFF, NULL, 0);
  for(int i = 0; i < nFiles; i++)
  {
   DragQueryFile((HDROP)Msg.WParam, i, buffer, 256);
   // tutaj należy umieszczać kod odpowiedzialny za wykonywanie operacji na upuszczonym pliku
   Label1->Caption = (AnsiString)buffer;
   Image1->Picture->LoadFromFile((AnsiString)buffer);
  }
  DragFinish((HDROP)Msg.WParam);
 }
 Df(Msg);
}
//--------------------------------

 

W ten sposób można przechwytywać ścieżkę dostępu do dowolnego upuszczonego pliku na formularzu i zaznaczam tutaj, że upuszczenie pliku na formularzu nie oznacza, że ten plik zostaje włączony do zasobów aplikacji, lecz program przechwytuje tylko ścieżkę dostępu do tego pliku, co w zupełności wystarcza, żeby wykonać dowolne operacje na takim pliku.

...powrót do menu. 

Minimalizacja okna głównego bez minimalizacji okien wtórnych.

 

Każda aplikacja okienkowa posiada główne okno programu, jeżeli aplikacja posiada więcej niż jedno okno i wywołamy to okno (wtórne - drugie), i zminimalizujemy okno główne, to okno wtórne również się zminimalizuje, ponieważ minimalizacja okna głównego oznacza minimalizację całego programu. Istnieje jednak sposób żeby to obejść, należy umieścić w formularzu wtórnym funkcję CreateParams, która to funkcja jest ładowana przy uruchamianiu aplikacji, a konkretnie przy tworzeniu okna. Funkcję deklarujemy w pliku nagłówkowym i definiujemy ją w pliku źródłowym, robimy to we wszystkich oknach wtórnych, funkcji nie należy umieszczać w formularzu głównym.

// Plik nagłówkowy np Unit2.h
//--------------------------------
private:
        void __fastcall
CreateParams(TCreateParams &Params);
//--------------------------------


// Plik źródłowy Unit2.cpp
//--------------------------------
void __fastcall TForm2::CreateParams(TCreateParams &Params)
{
 TForm::CreateParams(Params);
 Params.ExStyle |= WS_EX_APPWINDOW;
 Params.WndParent = GetDesktopWindow();
}
//--------------------------------

 

Tej funkcji się ni wywołuje, jest ona wywoływana automatycznie podczas tworzenia okna programu.

...powrót do menu. 

 

Okna MDI (Child) bez paska tytułowego i ramki - opracował: Paweł Pecio

 

Ostatnio sporo czasu potrzebowałem na odnalezienie w Internecie sposobu na uzyskanie okna potomnego MDI (MDI Child) bez paska tytułowego i ramki. Zwykłe ustawienie BorderStyle na bsNone nie działa. Po kilku godzinach poszukiwań znalazłem kilka przydatnych wskazówek i udało mi się sklecić takie rozwiązanie tego problemu:
W pliku nagłówkowym formy mającej być oknem MDI trzeba dodać w sekcji public:

 

// Plik nagłówkowy np Unit2.h
//--------------------------------
private:
        void __fastcall
CreateParams(TCreateParams &Params);
//--------------------------------

 

W pliku źródłowym:


// Plik źródłowy Unit2.cpp
//--------------------------------
void __fastcall TMDIForm1::CreateParams(TCreateParams &Params)
{
 TForm::CreateParams(Params);                 // Tworzy listę stylów wywoływanych podczas tworzenia okna przez windowsowe API.
 Params.Style = Params.Style ^ WS_CAPTION;    // Teraz trzeba usunąć styl belki tytułowej
 Params.Style = Params.Style ^ WS_THICKFRAME; // oraz styl ramki
}
//--------------------------------

 

W ten sposób można też modyfikować też inne style tworzonych okien.
Struktura Params zawiera też kilka innych ciekawych pozycji jak np. Caption, ale nie sprawdzałem czy działa, zapewne potem VCL i tak ustawia tytuł okna na zdefiniowany we właściwościach formularza, oraz StyleEx, która zawiera dodatkowe style okien.

...powrót do menu.