C++Builder 6 – referenční příručka

1. IDE a projekt

C++Builder 6 (Borland, 2002) je RAD prostředí nad knihovnou VCL a překladačem BCC32 5.6. Formulář se navrhuje myší, kód událostí se píše v C++.

SouborObsah
Project1.bprProjekt (XML/makefile): nastavení, seznam jednotek
Project1.cppWinMain, vytvoření formulářů, Application->Run()
Unit1.cpp / Unit1.hKód a deklarace třídy formuláře
Unit1.dfmVlastnosti komponent na formuláři (binárně nebo textově)
Project1.resIkona, verze, další prostředky
*.obj, *.tds, *.il?Výstupy překladu a ladicí info (lze mazat)
*.bpk / *.bplZdroj / binárka balíčku komponent
KlávesaAkce
F9Přeložit a spustit
Ctrl+F9Přeložit (make)
Shift+F9Build – přeložit vše
F12Přepnout formulář / kód
F11Object Inspector
Ctrl+F12 / Shift+F12Seznam jednotek / formulářů
F5Breakpoint
F7 / F8Krok do / přes
Ctrl+F2Ukončit laděný program
Ctrl+SpaceCode Completion
Ctrl+Shift+CClass Completion
Ctrl+Shift+↑/↓Přeskok mezi deklarací a definicí

Hlavní soubor projektu vypadá takto:

#include <vcl.h>
#pragma hdrstop
USEFORM("Unit1.cpp", Form1);

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    try {
        Application->Initialize();
        Application->Title = "Moje aplikace";
        Application->CreateForm(__classid(TForm1), &Form1);
        Application->Run();
    }
    catch (Exception &e) {
        Application->ShowException(&e);
    }
    return 0;
}

2. VCL a rozšíření jazyka

Každý formulář je třída odvozená od TForm. Komponenty z něj jsou ukazatele v sekci __published, kterou spravuje IDE. VCL objekty se vždy vytvářejí přes new a nikdy ne na zásobníku.

Klíčové slovoVýznam
__fastcallVolací konvence VCL (registry), povinná u metod a událostí VCL
__publishedSekce viditelná v Object Inspectoru, ukládá se do .dfm
__propertyVlastnost s čtecí/zapisovací metodou nebo položkou
__closureUkazatel na metodu konkrétního objektu (typ události)
__classid(T)Metatřída (TMetaClass*) pro CreateForm, InheritsFrom
dynamic_cast<T*>Bezpečné přetypování Sender

Hlavička formuláře (Unit1.h):

#ifndef Unit1H
#define Unit1H
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>

class TForm1 : public TForm
{
__published:    // spravuje IDE
    TButton *Button1;
    TEdit *Edit1;
    void __fastcall Button1Click(TObject *Sender);
private:
    int FPocet;
    void __fastcall SetPocet(int Value);
public:
    __fastcall TForm1(TComponent* Owner);
    __property int Pocet = { read = FPocet, write = SetPocet };
};
extern PACKAGE TForm1 *Form1;
#endif

Implementace (Unit1.cpp):

#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner), FPocet(0)
{
}

void __fastcall TForm1::SetPocet(int Value)
{
    FPocet = Value;
    Caption = "Počet: " + IntToStr(FPocet);
}

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    Pocet = Pocet + 1;   // volá SetPocet
}

Dynamické vytvoření komponenty a přiřazení události za běhu. Vlastník (this) komponentu sám uvolní, Parent určuje, kde se zobrazí:

void __fastcall TForm1::FormCreate(TObject *Sender)
{
    TButton *b = new TButton(this);
    b->Parent  = this;
    b->SetBounds(10, 10, 120, 25);
    b->Caption = "Dynamické";
    b->Tag     = 42;
    b->OnClick = DynClick;      // metoda: void __fastcall DynClick(TObject*)
}

void __fastcall TForm1::DynClick(TObject *Sender)
{
    TButton *b = dynamic_cast<TButton*>(Sender);
    if (b) ShowMessage("Tag = " + IntToStr(b->Tag));
}

Další formulář můžeš otevřít modálně, nebo vytvořit až za běhu. Druhý formulář v tom případě odeber z Project → Options → Forms → Auto-create:

#include "Unit2.h"
TForm2 *f = new TForm2(this);
try {
    if (f->ShowModal() == mrOk)
        Edit1->Text = f->Edit1->Text;
}
__finally {
    delete f;
}

3. Základní komponenty

Všechny vizuální komponenty sdílejí vlastnosti Left, Top, Width, Height, Visible, Enabled, Align, Anchors, Hint, Tag a události OnClick a OnKeyDown. Tabulka uvádí jen to, co je pro komponentu specifické.

Komponenta (záložka)Klíčové vlastnostiHlavní události
TLabel (Standard)Caption, Font, AutoSize, WordWrapOnClick
TButton (Standard)Caption, Default, Cancel, ModalResultOnClick
TEdit (Standard)Text, MaxLength, PasswordChar, ReadOnlyOnChange, OnKeyPress, OnExit
TMemo (Standard)Lines (TStrings), ScrollBars, WordWrapOnChange
TListBox (Standard)Items, ItemIndex, MultiSelect, Selected[i], SortedOnClick, OnDblClick
TComboBox (Standard)Items, ItemIndex, Text, Style (csDropDownList)OnChange
TCheckBox (Standard)Checked, State, AllowGrayedOnClick
TRadioGroup (Standard)Items, ItemIndex, ColumnsOnClick
TPanel (Standard)Align, BevelOuter, CaptionOnResize
TMainMenu / TPopupMenuItems (TMenuItem)OnClick položky
TStringGrid (Additional)Cells[c][r], ColCount, RowCount, FixedRows, OptionsOnSelectCell, OnDrawCell
TPageControl (Win32)ActivePage, ActivePageIndexOnChange
TStatusBar (Win32)SimpleText, Panels[i]->Text–
TTimer (System)Interval (ms), EnabledOnTimer

TEdit a TMemo:

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    Memo1->Lines->Add("Zadáno: " + Edit1->Text);
    Edit1->Clear();
    Edit1->SetFocus();
}

// povolit jen číslice v TEdit
void __fastcall TForm1::Edit1KeyPress(TObject *Sender, char &Key)
{
    if ((Key < '0' || Key > '9') && Key != VK_BACK)
        Key = 0;
}

TListBox a TComboBox:

ListBox1->Items->Add("Jablko");
ListBox1->Items->Add("Hruška");
if (ListBox1->ItemIndex >= 0)
    Label1->Caption = ListBox1->Items->Strings[ListBox1->ItemIndex];
ListBox1->Items->Delete(0);

ComboBox1->Style = csDropDownList;
ComboBox1->Items->Assign(ListBox1->Items);
ComboBox1->ItemIndex = 0;

TStringGrid:

StringGrid1->ColCount = 3;
StringGrid1->RowCount = 5;
StringGrid1->Cells[0][0] = "Jméno";     // [sloupec][řádek]
StringGrid1->Cells[1][0] = "Věk";
for (int r = 1; r < StringGrid1->RowCount; r++)
    StringGrid1->Cells[0][r] = "Položka " + IntToStr(r);
StringGrid1->Options << goEditing << goColSizing;   // Set<> operátor <<

Dialogy a zprávy:

ShowMessage("Hotovo");

if (MessageDlg("Uložit změny?", mtConfirmation,
               TMsgDlgButtons() << mbYes << mbNo, 0) == mrYes)
    Ulozit();

AnsiString jmeno = InputBox("Dotaz", "Zadej jméno:", "");

Application->MessageBox("Text", "Titulek", MB_OK | MB_ICONINFORMATION);

4. Řetězce, konverze a datum

AnsiString (alias String) je základní řetězec VCL. Indexuje se od 1, ne od 0, a sčítání má operátorem +. Na char* ho převedeš metodou c_str().

Metoda / funkcePříkladVýsledek
Length()s.Length()počet znaků
SubString(od, delka)"Ahoj".SubString(2, 2)"ho"
Pos(podretezec)s.Pos("x")pozice od 1, 0 = nenalezeno
UpperCase() / LowerCase()s.UpperCase()nový řetězec
Trim() / TrimLeft() / TrimRight()s.Trim()bez mezer na okrajích
Insert(t, pos) / Delete(pos, n)s.Delete(1, 3)mění s
IsEmpty()s.IsEmpty()bool
ToInt() / ToDouble()s.ToInt()číslo, při chybě výjimka
StringReplaceStringReplace(s, ",", ".", TReplaceFlags() << rfReplaceAll)nový řetězec
IntToStr / StrToIntIntToStr(42)"42"
StrToIntDefStrToIntDef("abc", 0)0 (bez výjimky)
FloatToStr / StrToFloatFloatToStr(3.5)"3,5" (podle DecimalSeparator)
FormatFloatFormatFloat("0.00", x)"3,14"
FormatFormat("%s má %d let", ARRAYOFCONST((jm, vek)))formátovaný text
IntToHexIntToHex(255, 4)"00FF"

Ukázka:

AnsiString s = "  Ahoj světe  ";
s = s.Trim();                         // "Ahoj světe"
int p = s.Pos(" ");                   // 5
AnsiString prvni = s.SubString(1, p - 1);   // "Ahoj"
char c = s[1];                        // 'A' – indexace od 1!

const char *cs = s.c_str();           // pro WinAPI / C funkce
std::string std_s = s.c_str();        // převod na std::string
AnsiString zpet = std_s.c_str();

// bezpečný převod vstupu
double cena;
try {
    cena = StrToFloat(Edit1->Text);
} catch (EConvertError &e) {
    ShowMessage("Neplatné číslo");
}

Label1->Caption = Format("Cena: %.2f Kč, kusů: %d",
                         ARRAYOFCONST((cena, 3)));

TDateTime je double: celá část jsou dny od 30. 12. 1899, desetinná část je čas.

TDateTime ted = Now();                // datum + čas
TDateTime dnes = Date();              // jen datum
Label1->Caption = FormatDateTime("dd.mm.yyyy hh:nn:ss", ted);
TDateTime zitra = dnes + 1;
int dni = (int)(zitra - dnes);
unsigned short r, m, d;
DecodeDate(dnes, r, m, d);
TDateTime dt = EncodeDate(2024, 12, 24);
TDateTime x  = StrToDate("24.12.2024");   // podle místního nastavení

TStringList je univerzální seznam řetězců. Hodí se na řazení, dvojice klíč=hodnota i rozdělení textu:

TStringList *sl = new TStringList;
try {
    sl->Sorted = true;
    sl->Duplicates = dupIgnore;
    sl->Add("Petr"); sl->Add("Anna"); sl->Add("Petr");
    // sl: Anna, Petr

    sl->Clear(); sl->Sorted = false;
    sl->Values["jmeno"] = "Jan";          // "jmeno=Jan"
    AnsiString j = sl->Values["jmeno"];

    sl->CommaText = "a,b,c";              // rozdělení podle čárky
    for (int i = 0; i < sl->Count; i++)
        Memo1->Lines->Add(sl->Strings[i]);

    int idx = sl->IndexOf("b");           // 1, -1 = nenalezeno
}
__finally {
    delete sl;
}

5. Soubory, dialogy, INI a registr

Textové soubory je nejjednodušší načíst a uložit přes TStrings (Memo1->Lines, TStringList). Na binární data použij TFileStream.

FunkceÚčel
FileExists(cesta) / DirectoryExists(cesta)Existence souboru / složky
ForceDirectories(cesta)Vytvoření složek včetně nadřazených
DeleteFile(cesta) / RenameFile(stare, nove)Smazání / přejmenování
ExtractFilePath / ExtractFileName / ExtractFileExtČásti cesty
ChangeFileExt(cesta, ".bak")Změna přípony
ExtractFilePath(Application->ExeName)Složka programu
FindFirst / FindNext / FindCloseProcházení souborů ve složce

Načtení a uložení textu s dialogy:

void __fastcall TForm1::OtevritClick(TObject *Sender)
{
    OpenDialog1->Filter = "Textové soubory (*.txt)|*.txt|Všechny soubory (*.*)|*.*";
    if (OpenDialog1->Execute())
        Memo1->Lines->LoadFromFile(OpenDialog1->FileName);
}

void __fastcall TForm1::UlozitClick(TObject *Sender)
{
    SaveDialog1->DefaultExt = "txt";
    if (SaveDialog1->Execute())
        Memo1->Lines->SaveToFile(SaveDialog1->FileName);
}

Binární zápis a čtení struktury:

struct TZaznam { int id; double hodnota; char jmeno[32]; };

void Uloz(const AnsiString &soubor, const TZaznam &z)
{
    TFileStream *fs = new TFileStream(soubor, fmCreate);
    try {
        fs->WriteBuffer(&z, sizeof(z));
    }
    __finally {
        delete fs;
    }
}

bool Nacti(const AnsiString &soubor, TZaznam &z)
{
    if (!FileExists(soubor)) return false;
    TFileStream *fs = new TFileStream(soubor, fmOpenRead | fmShareDenyWrite);
    try {
        fs->ReadBuffer(&z, sizeof(z));
    }
    __finally {
        delete fs;
    }
    return true;
}

Výpis souborů ve složce:

TSearchRec sr;
if (FindFirst("C:\\Data\\*.txt", faAnyFile, sr) == 0) {
    do {
        ListBox1->Items->Add(sr.Name + "  (" + IntToStr(sr.Size) + " B)");
    } while (FindNext(sr) == 0);
    FindClose(sr);
}

Nastavení v INI souboru (#include <IniFiles.hpp>):

TIniFile *ini = new TIniFile(ChangeFileExt(Application->ExeName, ".ini"));
try {
    ini->WriteInteger("Okno", "Left", Left);
    ini->WriteString("Uzivatel", "Jmeno", Edit1->Text);
    Left = ini->ReadInteger("Okno", "Left", 100);
    bool b = ini->ReadBool("Volby", "Zvuk", true);
}
__finally {
    delete ini;
}

Registr Windows (#include <Registry.hpp>):

TRegistry *reg = new TRegistry;
try {
    reg->RootKey = HKEY_CURRENT_USER;
    if (reg->OpenKey("Software\\MojeFirma\\MojeApp", true)) {   // true = vytvořit
        reg->WriteString("Posledni", OpenDialog1->FileName);
        if (reg->ValueExists("Pocet"))
            int n = reg->ReadInteger("Pocet");
        reg->CloseKey();
    }
}
__finally {
    delete reg;
}

6. Výjimky a ladění

VCL výjimky jsou třídy odvozené od Exception a chytá se vždy referencí. Rozšíření __finally zaručí uvolnění prostředků a nahrazuje RAII u VCL objektů.

VýjimkaKdy vzniká
EConvertErrorStrToInt, StrToFloat, StrToDate s neplatným vstupem
EStringListErrorIndex mimo rozsah v TStrings
EFOpenError / EFCreateErrorSoubor nelze otevřít / vytvořit
EAccessViolationPřístup na neplatnou adresu (např. smazaný objekt)
EZeroDivide / EDivByZeroDělení nulou (float / int)
EDatabaseErrorChyby databázových komponent
EAbortTichá výjimka z Abort(), nezobrazí se
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    TStringList *sl = new TStringList;
    try {
        try {
            sl->LoadFromFile("data.txt");
            int n = StrToInt(sl->Strings[0]);
            if (n < 0)
                throw Exception("Záporná hodnota: " + IntToStr(n));
        }
        catch (EFOpenError &e) {
            ShowMessage("Soubor nelze otevřít: " + e.Message);
        }
        catch (EConvertError &e) {
            ShowMessage("Špatný formát čísla");
        }
        catch (Exception &e) {
            ShowMessage(e.ClassName() + ": " + e.Message);
        }
    }
    __finally {
        delete sl;                // provede se vždy
    }
}

Vlastní třída výjimky:

class EMojeChyba : public Exception {
public:
    int Kod;
    __fastcall EMojeChyba(const AnsiString &Msg, int kod)
        : Exception(Msg), Kod(kod) {}
};

throw EMojeChyba("Neplatný stav", 17);

Globální zachycení nezachycených výjimek (např. pro logování):

// v deklaraci: void __fastcall AppException(TObject *Sender, Exception *E);
void __fastcall TForm1::FormCreate(TObject *Sender)
{
    Application->OnException = AppException;
}

void __fastcall TForm1::AppException(TObject *Sender, Exception *E)
{
    TStringList *log = new TStringList;
    try {
        AnsiString f = ExtractFilePath(Application->ExeName) + "chyby.log";
        if (FileExists(f)) log->LoadFromFile(f);
        log->Add(DateTimeToStr(Now()) + "  " + E->Message);
        log->SaveToFile(f);
    }
    __finally { delete log; }
    Application->ShowException(E);
}

Nástroje ladění:

7. Grafika a časovač

Kresli v události OnPaint (formuláře nebo TPaintBox). Jinak se kresba při překreslení okna ztratí. Kresba do TImage->Canvas se pamatuje v bitmapě a zůstává.

Člen TCanvasPoužití
Pen->Color, Pen->Width, Pen->StyleBarva a styl čáry
Brush->Color, Brush->StyleVýplň (bsClear = průhledná)
Font->Name, Font->Size, Font->Color, Font->StylePísmo
MoveTo(x,y), LineTo(x,y)Čára
Rectangle, Ellipse, RoundRect, PolygonTvary
FillRect(TRect)Vyplnění obdélníku štětcem
TextOut(x,y,s), TextWidth(s), TextHeight(s)Text a jeho rozměry
Pixels[x][y]Jednotlivý pixel (pomalé)
Draw(x,y,graphic), StretchDraw(rect,graphic)Vykreslení bitmapy

Barvy: konstanty clRed, clBlue, clBtnFace apod. nebo (TColor)RGB(r, g, b).

void __fastcall TForm1::PaintBox1Paint(TObject *Sender)
{
    TCanvas *c = PaintBox1->Canvas;
    c->Brush->Color = clWhite;
    c->FillRect(PaintBox1->ClientRect);

    c->Pen->Color = clBlue;
    c->Pen->Width = 2;
    c->MoveTo(0, PaintBox1->Height / 2);
    for (int x = 0; x < PaintBox1->Width; x++) {
        int y = PaintBox1->Height / 2 - 40 * sin(x / 20.0);   // #include <math.h>
        c->LineTo(x, y);
    }

    c->Brush->Color = (TColor)RGB(255, 200, 0);
    c->Ellipse(10, 10, 60, 60);

    c->Brush->Style = bsClear;
    c->Font->Size = 12;
    c->Font->Style = TFontStyles() << fsBold;
    c->TextOut(70, 20, "sin(x)");
}

Plynulá kresba přes bitmapu v paměti (bez blikání):

Graphics::TBitmap *bmp = new Graphics::TBitmap;
try {
    bmp->Width  = PaintBox1->Width;
    bmp->Height = PaintBox1->Height;
    bmp->Canvas->Brush->Color = clBlack;
    bmp->Canvas->FillRect(Rect(0, 0, bmp->Width, bmp->Height));
    // ... kreslení do bmp->Canvas ...
    PaintBox1->Canvas->Draw(0, 0, bmp);
}
__finally {
    delete bmp;
}

Načtení obrázku do TImage. Pro JPEG přidej #include <jpeg.hpp>:

Image1->Picture->LoadFromFile("foto.jpg");
Image1->Stretch = true;
Image1->Proportional = true;
Image1->Picture->SaveToFile("kopie.bmp");   // jen bitmapa

Animace časovačem a překreslení:

// v .h: int FX;  (člen třídy, nastav na 0 v konstruktoru)

void __fastcall TForm1::FormCreate(TObject *Sender)
{
    Timer1->Interval = 30;       // ms
    Timer1->Enabled  = true;
    DoubleBuffered   = true;     // méně blikání
}

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
    FX = (FX + 3) % ClientWidth;
    Invalidate();                // vyvolá OnPaint
    StatusBar1->SimpleText = TimeToStr(Time());
}

void __fastcall TForm1::FormPaint(TObject *Sender)
{
    Canvas->Brush->Color = clRed;
    Canvas->Ellipse(FX, 50, FX + 30, 80);
}

8. Databáze

Všechny tři technologie používají stejný řetězec: dataset (tabulka nebo dotaz) → TDataSource → data-aware komponenty (TDBGrid, TDBEdit, TDBNavigator). Pro nové projekty a MS SQL/Access doporučuji ADO. BDE je zastaralé a pod Wine/Win10+ problematické.

TechnologieZáložkaKomponentyVhodné pro
ADOADOTADOConnection, TADOQuery, TADOTable, TADOCommandMS SQL, Access, Excel, ODBC
BDEBDETDatabase, TTable, TQueryParadox, dBase, staré aplikace
dbExpressdbExpressTSQLConnection, TSQLQuery + TClientDataSetInterBase, Oracle, MySQL 3.x, DB2
InterBase ExpressInterBaseTIBDatabase, TIBQuery, TIBTransactionInterBase / Firebird

Běžné operace s datasetem (společné pro TTable, TQuery, TADOQuery…):

Metoda / vlastnostÚčel
Open() / Close() / ActiveOtevření / zavření
First(), Next(), Prior(), Last(), Eof, BofPohyb po záznamech
FieldByName("x")->AsString / AsInteger / AsFloat / AsDateTimeČtení a zápis pole
Append() / Insert() / Edit() / Post() / Cancel() / Delete()Úpravy
Locate("pole", hodnota, TLocateOptions())Vyhledání záznamu
RecordCount, IsEmpty()Počet záznamů
DisableControls() / EnableControls()Zrychlení hromadného průchodu

ADO – připojení a dotaz s parametrem:

#include <ADODB.hpp>

ADOConnection1->ConnectionString =
    "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\Data\\firma.mdb;";
// MS SQL: "Provider=SQLOLEDB;Data Source=server;Initial Catalog=db;"
//         "Integrated Security=SSPI;"
ADOConnection1->LoginPrompt = false;
ADOConnection1->Connected = true;

ADOQuery1->Connection = ADOConnection1;
ADOQuery1->SQL->Text = "SELECT * FROM Zakaznici WHERE Mesto = :mesto";
ADOQuery1->Parameters->ParamByName("mesto")->Value = "Praha";
ADOQuery1->Open();

DataSource1->DataSet = ADOQuery1;
DBGrid1->DataSource  = DataSource1;

Průchod výsledkem:

ADOQuery1->DisableControls();
try {
    double suma = 0;
    for (ADOQuery1->First(); !ADOQuery1->Eof; ADOQuery1->Next())
        suma += ADOQuery1->FieldByName("Castka")->AsFloat;
    Label1->Caption = FormatFloat("#,##0.00 Kč", suma);
}
__finally {
    ADOQuery1->EnableControls();
}

Zápis – přes dataset, nebo příkazem SQL:

ADOTable1->Append();
ADOTable1->FieldByName("Jmeno")->AsString = Edit1->Text;
ADOTable1->FieldByName("Vek")->AsInteger  = StrToInt(Edit2->Text);
ADOTable1->Post();

ADOCommand1->Connection  = ADOConnection1;
ADOCommand1->CommandText = "UPDATE Zakaznici SET Aktivni = 0 WHERE Id = :id";
ADOCommand1->Parameters->ParamByName("id")->Value = 15;
ADOCommand1->Execute();

Transakce:

ADOConnection1->BeginTrans();
try {
    // ... více příkazů ...
    ADOConnection1->CommitTrans();
}
catch (...) {
    ADOConnection1->RollbackTrans();
    throw;
}

BDE – TQuery nad aliasem (alias se nastavuje v BDE Administrator):

Query1->DatabaseName = "DBDEMOS";
Query1->SQL->Clear();
Query1->SQL->Add("SELECT Name, Capital FROM country WHERE Population > :p");
Query1->ParamByName("p")->AsInteger = 10000000;
Query1->Open();
// pro INSERT/UPDATE/DELETE: Query1->ExecSQL();

9. Vlákna (TThread)

Dlouhé operace patří do potomka TThread. Ke komponentám VCL smíš přistupovat jen z hlavního vlákna, takže aktualizace GUI jde vždy přes Synchronize(). Šablonu vytvoříš v File → New → Other → Thread Object.

ČlenVýznam
TThread(bool CreateSuspended)true = spustit až voláním Resume()
Execute()Tělo vlákna (přepisuješ)
Synchronize(metoda)Spustí metodu v hlavním vlákně (BCB6: metoda bez parametrů)
Terminate() / TerminatedPožadavek na ukončení / jeho test v Execute
FreeOnTerminateObjekt se po skončení sám smaže
WaitFor()Čekání na dokončení
OnTerminateUdálost po skončení (běží v hlavním vlákně)
PrioritytpLower, tpNormal, tpHigher…

Vlákno s průběhem v TProgressBar:

// Vypocet.h
class TVypocet : public TThread
{
private:
    int FPozice;
    void __fastcall Aktualizuj();
protected:
    void __fastcall Execute();
public:
    __fastcall TVypocet(bool CreateSuspended);
};

// Vypocet.cpp
#include "Unit1.h"

__fastcall TVypocet::TVypocet(bool CreateSuspended)
    : TThread(CreateSuspended)
{
    FreeOnTerminate = true;
}

void __fastcall TVypocet::Execute()
{
    for (int i = 0; i <= 100 && !Terminated; i++) {
        Sleep(50);                 // náročná práce
        FPozice = i;
        Synchronize(Aktualizuj);   // bezpečný přístup ke GUI
    }
}

void __fastcall TVypocet::Aktualizuj()
{
    Form1->ProgressBar1->Position = FPozice;
}

Spuštění a zastavení z formuláře:

// v Unit1.h: TVypocet *FVlakno;  (člen TForm1, v konstruktoru = NULL)

void __fastcall TForm1::StartClick(TObject *Sender)
{
    FVlakno = new TVypocet(true);
    FVlakno->OnTerminate = VlaknoSkoncilo;
    FVlakno->Resume();
}

void __fastcall TForm1::StopClick(TObject *Sender)
{
    if (FVlakno) FVlakno->Terminate();
}

void __fastcall TForm1::VlaknoSkoncilo(TObject *Sender)
{
    FVlakno = NULL;            // objekt se smaže sám (FreeOnTerminate)
    ShowMessage("Hotovo");
}

Sdílená data mezi vlákny chraň kritickou sekcí (#include <SyncObjs.hpp>):

TCriticalSection *Zamek = new TCriticalSection;

Zamek->Acquire();
try {
    SdilenySeznam->Add("položka");
}
__finally {
    Zamek->Release();
}

10. Tipy a časté problémy

Samostatné EXE bez DLL/BPL. Aby program běžel na jiném PC bez instalace C++Builderu:

  1. Project → Options → Packages: vypni Build with runtime packages.

  2. Project → Options → Linker: vypni Use dynamic RTL (jinak je potřeba cc3260mt.dll).

  3. Project → Options → Compiler: tlačítko Release (bez ladicích informací).

  4. Udělej Build (Shift+F9), ne jen Make.

Při použití BDE je navíc potřeba nainstalovat BDE. ADO využívá MDAC, který je součástí Windows.

ProblémPříčina / řešení
[Linker Fatal Error] Unable to open file 'XYZ.OBJ'Chybějící jednotka nebo knihovna – přidej ji do projektu nebo doplň Library path
Unresolved external '__fastcall TForm1::Button1Click(...)'Deklarace v .h bez definice v .cpp (smazaný handler) – dopiš tělo nebo smaž deklaraci
Handler zmizel po uloženíIDE maže prázdné handlery – napiš do něj alespoň komentář
Access violation at address ...Práce s již smazaným objektem, NULL ukazatel, přístup ke GUI z vlákna
VCL style classes must be constructed using operator newVCL objekt vytvořený na zásobníku – použij new
Pomalý překladZapni Pre-compiled headers a drž #pragma hdrstop hned za #include <vcl.h>
.dfm nejde otevřít / konflikt v GituUlož formuláře jako text: pravé tlačítko na formuláři → Text DFM
Čeština se zobrazuje špatněVCL 6 není Unicode – nastav Font->Charset = EASTEUROPE_CHARSET, zdrojové soubory ukládej v CP1250
Desetinná čárka vs. tečkaStrToFloat používá DecimalSeparator – případně nastav DecimalSeparator = '.';

Rozdíly oproti modernímu C++. BCC32 5.6 podporuje C++98 jen částečně. Chybí auto, lambdy, nullptr a std::unique_ptr (je jen std::auto_ptr). Složitější šablony a část Boostu nepřeložíš. STL (STLport) je k dispozici: #include <vector>, <map>, <string>. Členy třídy nejde inicializovat v deklaraci, dělej to v konstruktoru.

C++Builder 6 pod Linuxem (Wine). Instalátor IDE běží v 32bitovém prefixu. Hotové VCL aplikace běží pod Wine většinou bez problémů:

export WINEPREFIX=~/.wine-bcb6 WINEARCH=win32
winecfg                               # Windows XP
wine /media/cdrom/install.exe
winetricks mdac28 jet40               # pro ADO/Access
wine explorer /desktop=bcb6,1920x1080 \
  "C:\\Program Files\\Borland\\CBuilder6\\Bin\\bcb.exe"

BDE pod Wine vyžaduje v BDE Administrator nastavit LOCAL SHARE = TRUE a změnit NET DIR na zapisovatelnou složku.

Překlad z příkazové řádky:

bpr2mak Project1.bpr
make -f Project1.mak
REM nebo jednoduchý konzolový program:
bcc32 -WC -O2 program.cpp