Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Теллес М. - Borland C++ Builder. Библиотека программиста - 1998

.pdf
Скачиваний:
764
Добавлен:
13.08.2013
Размер:
4.35 Mб
Скачать

Borland C++ Builder (+CD). Библиотека программиста 51

FpXBitmap->Canvas->LineTo(0, FpXBitmap->Height); // Отображаем рисунок Y (нолик) FpYBitmap->Canvas-> Ellipse(0,0,FpYBitmap->Width,FpYBitmap->Height);

}

Первая пара блоков конструктора сравнительно понятна. Перед тем как двигаться дальше, мы присваиваем переменным класса разумные начальные значения. Трудная часть начинается с создания объекта TBitmap.

Жизнь и приключения объекта TBitmap

Почему мы должны устанавливать все эти свойства TBitmap при его создании? Раньше все, что мы делали, — это создавали новый объект и потом либо загружали его с диска, либо ассоциировали с ним растровый рисунок.

Как и все порядочные растровые рисунки в Windows, объект TBitmap начинает жизнь с растрового рисунка Windows 1ґ1, и, чтобы что-нибудь в нем нарисовать, мы должны «растянуть» его до размера, который собираемся использовать. В данном случае мы должны увеличить каждый из двух растровых рисунков до одной трети клиентской области нашей формы. Почему клиентской? Дело в том, что свойство Width (ширина) формы включает в себя рамки вокруг формы, а свойство Height (высота) формы включает в себя панель заголовка. Если использовать эти свойства, первые две кнопки выглядели бы шире, чем третья. Поскольку это не то, что нам надо, придется поискать другие пути. Объекты CBuilder имеют два типа высоты и ширины.

Свойства ClientWidth и ClientHeight представляют собой соответственно ширину и высоту клиентской области формы. Клиентская область это то, что осталось бы на форме, если бы из нее удалили меню, рамки, заголовки и панель состояния.

После того как мы определили свойства Width и Height, объекты растровые рисунки (TBitmap) можно рассматривать как простые графические объекты, которые ожидают, чтобы мы их заполнили. При создании они имеют белый фон (background). Поскольку это будет плохо смотреться на стальном фоне нашей формы, мы должны переустановить цвет фона, используя свойство Brush (дословно кисть) рисунка, у которого есть собственное свойство Color (цвет). Свойство Color свойства Brush будет использовано для фона при всех операциях, связанных с рисованием, для данного рисунка. В нашем случае мы должны залить белый рисунок цветом фона формы. Это сделано посредством метода FillRect. Для использования FillRect инициализируйте прямоугольник (объект TRect) с границами, которые хотите заполнить. В данном случае мы используем границы самого рисунка, поскольку хотим залить его целиком.

r.Left = 0; r.Top = 0;

r.Right = FpXBitmap->Width; r.Bottom = FpXBitmap->Height;

После того как мы получили прямоугольник для заливки и цвет, указанный в свойстве Brush свойства Canvas объекта, который мы хотим залить (в нашем случае растрового рисунка), мы возбуждаем метод FillRect для этого объекта (Bitmap->Canvas), и все остальное делается автоматически.

Следующим шагом после заливки рисунка будет рисование того образа (image), который будет отображен на рисунке. Для этого вы можете использовать любой из определенных методов рисования Canvas или же передать свойство Canvas->Handle любой стандартной программе

Borland C++ Builder (+CD). Библиотека программиста 52

Windows, которая окажется под рукой. Это удобно, когда вы хотите использовать графическую функцию третьей программы в вашем приложении на CBuilder.

Для растрового рисунка X (крестик) мы используем стандартные команды Canvas MoveTo и LineTo для рисования двух диагональных линий, образующих требующийся нам крест. Для растрового рисунка Y (нолик) используется другой метод объекта Canvas, который называется Ellipse. Ellipse отобразит эллиптическую фигуру на заданном Canvas. Поскольку наши рисунки почти квадратные, результат функции Ellipse будет очень похож на круг. Чем ближе будут значения Width и Heidht, тем более приближенным к кругу будет и результат.

Итак, теперь мы имеем законченные растровые рисунки, которые можно использовать так же, как если бы мы загрузили их из файла или ассоциировали в редакторе компонентов. Это очаровательная черта объектно-ориентированного программирования. Вне зависимости от того, как вы добились промежуточного результата, исходить из него вы будете совершенно одинаковым образом. В такой гибкости и есть мощь объектно-ориентированного программирования.

Замечание

CBuilder также располагает методами для сохранения вашего рисунка на диске. Вы можете создать настоящий графический редактор на основе системы растровых рисунков CBuilder. Для сохранения растрового рисунка на диске создайте объект поток файлового ввода/вывода (file stream), используя класс TFileStream, а потом используйте метод SaveToStream объекта TBitmap для записи последнего на диск.

В конструкторе также вызывается вспомогательная функция ResetBoard. В ней нет никаких сюрпризов. Все, что мы делаем, — это присваиваем всем клеткам игрового поля определенное сигнальное значение, в данном случае 0.

void TForm1::ResetBoard(void)

{

// Инициализируем игровое поле for ( int i=0, i<3, ++i )

for ( int j=0, j<3, ++j ) FnGameBoard[i][j] = 0;

}

Худшая часть задачи осталась позади. Мы динамически создали объект растровый рисунок, сделали его нужного размера и нарисовали в его поле (Canvas) те изображения, которые должен видеть пользователь. Теперь следующий шаг это показать рисунок пользователю. Сначала давайте займемся рисованием формы, а потом перейдем к обработке ввода пользователя.

Добавьте в форму обработчик для события OnPaint. Самый простой способ это сделать перейти на страницу Events в Object Inspector, найти там событие OnPaint и дважды щелкнуть мышью в клеточке сетки справа от него. CBuilder автоматически создаст новый обработчик с корректным названием. В общем случае, присвоенное ему имя будет Formxxx, где xxx — это имя события без «On». Поэтому для события OnPaint корректное имя будет FormPaint. Вот как выглядит код для обработчика события FormPaint:

void __fastcall TForm1::FormPaint(TObject *Sender)

{

// Сначала рисуем вертикальные линии на форме

Canvas->MoveTo( ClientWidth/3,0 ); Canvas->LineTo( ClientWidth/3,ClientHeight );

Borland C++ Builder (+CD). Библиотека программиста 53

Canvas->MoveTo( (2*ClientWidth)/3,0); Canvas->LineTo( (2*ClientWidth)/3, ClientHeight ); // Теперь горизонтальные

Canvas->MoveTo( 0, ClientHeight/3 ); Canvas->LineTo( ClientWidth, ClientHeight/3 ); Canvas->MoveTo( 0, (2*ClientHeight)/3 ); Canvas->LineTo( ClientWidth, (2*ClientHeight)/3 );

//Отображаем рисунки по клеткам for ( int i=0, i<3, ++i )

for ( int j=0, j<3, ++j )

{

int nXPos = (ClientWidth/3)*j + 1; int nYPos = (ClientHeight/3)*i + 1;

//Если в клетке - крестик

if ( FnGameBoard[i][j] == 1 )

{

Canvas->Draw(nXPos, nYPos, FpXBitmap );

}

else

// Если в клетке - нолик

if (FnGameBoard[i][j] == 2 )

{

Canvas->Draw(nXPos, nYPos, FpYBitmap );

}

}

}

Никаких особых сюрпризов в этом коде нам не встретилось. Мы рисуем игровое поле, передвигаясь по клиентской области формы и рисуя горизонтальные и вертикальные линии. Потом мы проверяем клетки на предмет их принадлежности первому или второму игроку. Если клетка принадлежит первому игроку, мы рисуем в ней крестик, используя для этого растровый рисунок X, созданный нами в конструкторе. Если клетка принадлежит второму игроку, мы рисуем в ней нолик, используя для этого растровый рисунок Y, созданный нами в конструкторе. Позиция рисунка это позиция квадрата со смещением в один пиксел, чтобы не затереть линии поля.

Обработка щелчков мыши

На данном этапе мы завершили ту часть, которая связана с выводом на экране. Теперь перейдем к обработке собственно процесса игры. Нам надо отследить каждый щелчок мыши на игровом поле, определить клетку, в которой была нажата мышь, и закрепить эту клетку за тем игроком, чей ход. Конечно, при этом надо проверить, не занята ли уже клетка.

Форма не содержит события щелчка мыши1. Тем не менее существуют события нажатия кнопки мыши и отпускания нажатой кнопки мыши. Мы привяжем наш обработчик к событию MouseDown (нажатие кнопки мыши). Обычно не имеет значения, которое событие вы обрабатываете, если, конечно, у вас не предусмотрена разная конкретная реакция на события MouseDown и MouseUp (как было у нас в примере Scribble из предыдущей главы). Обращение к

1 Совершенно непонятно, почему автор так считает. У формы ЕСТЬ событие OnClick (возникает при щелчке мыши). В последующем коде все ссылки на событие OnMouseDown можно заменить на OnClick. Тем не менее пускай все останется как есть. — Примеч. перев.

Borland C++ Builder (+CD). Библиотека программиста 54

событию MouseDown обусловит чуть более быструю реакцию программы, чем обращение к событию MouseUp, но это не будет заметно среднему пользователю.

Добавьте обработчик для события MouseDown, дважды щелкнув на этом событии в окне Object Inspector. Следующий код добавьте в созданный при этом в исходном файле обработчик события

FormMouseDown:

void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button,

TShiftState Shift, int x, int y)

{

int nRow, int nCol,

//Определяем, что за клетка if ( X < ClientWidth/3 ) nCol = 0;

else

if ( X < (2*ClientWidth)/3 ) nCol = 1;

else nCol = 2;

if ( Y < ClientHeight/3 ) nRow = 0;

else

if ( Y < (2*ClientHeight)/3 ) nRow = 1;

else nRow = 2;

//Проверяем, не занята ли клетка

if ( FnGameBoard[nRow][nCol] != 0 )

{

MessageBeep(0);

}

else // Нет - присваиваем клетку этому игроку

{

FnGameBoard[nRow][nCol] = FnWhichPlayer; // Передаем ход

if ( FnWhichPlayer == 1 ) FnWhichPlayer = 2;

else FnWhichPlayer = 1; Invalidate();

// Проверим, не выиграл ли кто if ( CheckForWinner() )

{

ResetBoard(); FnWhichPlayer = 1; Invalidate();

}

}

}

Borland C++ Builder (+CD). Библиотека программиста 55

В этом обработчике сначала проверяется, какую клетку выбрал пользователь. Это выполняется сравнением X и Y координат , которые передает методу CBuilder, с клетками нашей сетки. Мы просто проверяем, где произошел щелчок в первой, второй или третьей трети сетки по вертикали, а потом и по горизонтали. Преобразовав полученные значения, мы получаем две координаты строку и столбец в сетке. Это отлично подходит для нашего метода хранения данных, которые представлены в виде двумерного массива.

После того как определены строка и столбец, игровое поле проверяется на предмет того, не занята ли уже выбранная клетка. Если это так, то вызывается небезызвестный вам метод MessageBeep, показывающий пользователю недопустимость повторного выбора. Если же клетка пуста, она присваивается ходящему игроку. Наконец, после этого мы вызываем еще один метод формы для проверки, не победил ли кто. Вот код для этого метода:

BOOL Tform1::CheckForWinner(void)

{

int Winner;

for ( int nPlayer = 1; nPlayer<3; ++nPlayer )

{

// Проверяем построчно

for ( int nRow = 0; nRow<3; ++nRow)

{

//Предполагаем, что этот игрок выиграл nWinner = nPlayer;

//Проверяем все столбцы. Если хотя бы в одном

//нет идентификатора игрока, он не победил здесь for (int nCol = 0; nCol<3; ++nCol )

if ( FnGameBoard[nRow][nCol] != nPlayer ) nWinner = 0;

if ( nWinner != 0 )

{

String s = "Игрок " + String(nWinner) + " выиграл"; MessageBox(NULL, s.c_str(), "Победитель", MB_OK );

return true;

}

}

//Теперь проверяем по столбцам for ( int nCol = 0; nCol<3; ++nCol )

{

//Предполагаем, что этот игрок выиграл nWinner = nPlayer;

//Проверяем все строки. Если хотя бы в одной

//нет идентификатора игрока, он не победил здесь for ( int nRow = 0; nRow<3; ++nRow )

if FnGameBoard[nRow][nCol] != nPlayer ) nWinner = 0;

if ( nWinner != 0 )

{

String s = "Игрок " + String(nWinner) + " выиграл"; MessageBox(NULL, s.c_str90, "Победитель", MB_OK ); return true;

}

}

//Наконец, проверяем диагонали

Borland C++ Builder (+CD). Библиотека программиста 56

if ( FnGameBoard[0][0] == nPlayer && FnGameBoard[1][1] == nPlayer && FnGameBoard[2][2] == nPlayer )

{

String s = "Игрок " + String(nPlayer) + " выиграл"; MessageBox(NULL, s.c_str90, "Победитель", MB_OK ); return true;

}

if ( FnGameBoard[0][2] == nPlayer && FnGameBoard[1][1] == nPlayer && FnGameBoard[2][0] == nPlayer )

{

String s = "Игрок " + String(nPlayer) + " выиграл"; MessageBox(NULL, s.c_str90, "Победитель", MB_OK ); return true;

}

}

//Если дошли досюда, проверяем, есть ли еще

//пустые клетки (не конец ли игры)

for ( int nRow = 0; nRow<3; ++nRow )

{

for ( int nCol = 0; nCol <3; ++nCol ) if ( FnGameBoard[nRow][nCol] == 0 ) return false;

}

// Уведомляем о конце игры

MessageBox(NULL, "Боевая ничья!", "Конец игры", MB_OK ); return true;

}

Этот метод интересен лишь с точки зрения использования в нем объекта String (строка) для формирования уведомления победителю. String один из самых полезных классов в CBuilder, вам надо привыкнуть к нему. Объекты String гораздо более гибкие, чем char* или массив символов (char []), которые вам, наверное, приходилось использовать в C или C++ в других средах. String также является частью библиотеки стандартных шаблонов, о которой мы поговорим гораздо подробнее чуть ниже.

Самой приятной из возможностей String является возможность преобразовывать в строку вещи, не являющиеся строками или символами. Передав аргумент типа integer в конструктор String, мы получаем представление этого числа в виде строки. Два объекта String могут быть объединены с помощью оператора «+». Используя две эти возможности, мы и создали строку вывода о победе первого игрока из строк «Игрок» и «выиграли цифры 1, что должно показаться вам очарователь ным, если вы привыкли использовать sprintf и волноваться о типах аргументов и длине строк.

Последняя проверка в методе проводится на предмет конца игры. Если все клетки заполнены, то игра окончена вничью. К сожалению, это очень частый исход в игре крестики-нолики я уверен, вы знаете это не хуже меня.

Наша игра уже более-менее закончена, но нам надо освободить ресурсы, отведенные для TBitmap в конструкторе объекта. Лучшее место для этого деструктор объекта, и это как раз то, что мы должны сделать. Вот код деструктора:

__fastcall TForm1::~TForm1(void)

Borland C++ Builder (+CD). Библиотека программиста 57

{

delete FpXBitmap; delete FpYBitmap;

}

Заметьте, что мы используем модификатор __fastcall для деструктора. ~TForm1 это переопределение деструктора базового класса TForm. Поскольку TForm — объект VCL, а все методы VCL должны быть определены с модификатором __fastcall, то вам также надо добавлять это в свои переопределенные методы. Если вы этого не сделаете, то компилятор выдаст вам сообщение об ошибке «Virtual function TForm1::~TForm1 conflicts with base class Forms::TForm» («Виртуальная функция конфликтует с базовым классом»). В случае появления таких ошибок проверьте модификаторы.

Все, что нам осталось сделать, — это дополнить заголовочный файл описаниями методов, которые мы добавили в форму в исходном файле. Вот обновленный заголовочный файл Unit1.h, все изменения в котором показаны подсветкой:

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

#ifndef Unit1H #define Unit1H

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

#include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp>

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

class TForm1 : public TForm

{

__published: // IDE-managed components void __fastcall FormPaint(TObject *Sender);

void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y); private: // User declarations

Graphics::TBitmap *FpXBitmap;

Graphics::TBitmap *FpYBitmap; int FnGameBoard[3][3];

int FnWhichPlayer; void ResetBoard(void);

BOOL CheckForWinner(void); public: // User declarations

__fastcall TForm1(TComponent *Owner); virtual __fastcall ~TForm1(void);

}; //--------------------------------------------------------

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

#endif

Запускаем игру

Последний шаг в программировании игр всегда самый веселый, ибо это тестирование программного продукта. Запустите программу и заполните несколько клеток. Попробуйте выбрать одну и ту же клетку дважды. Заполните клетки так, чтобы победная комбинация находилась в

Borland C++ Builder (+CD). Библиотека программиста 58

строке, потом в столбце, потом на диагонали. Убедитесь, что все варианты работают. Наконец, заполните все игровое поле так, чтобы на нем не было победной комбинации, чтобы удостовериться в том, что проверка окончания игры работает. После того как все проверили, пригласите друга или ребенка на игру и наслаждайтесь. На рис. 3.5 показан типичный пример игры в процессе, с несколькими крестиками и ноликами, а на рис. 3.6 показана игра, завершившаяся победой одного из игроков.

Рис. 3.5. Типичный пример игры крестики-нолики

Рис. 3.6. Победа первого игрока

Что мы узнали в этой главе?

CBuilder предоставляет богатый ассортимент графических объектов и компонентов, которые позволяют легко определять и отображать на экране в вашем приложении растровые рисунки, иконки и другую графику.

Вот кое-что из того, что мы освоили в этой главе:

Загрузка растровых рисунков (bitmap) прямо из файла во время исполнения программы.

Отображение растровых рисунков на экране при посредстве объекта Canvas.

Создание растрового рисунка во время исполнения и рисование в его поле.

Обработка вывода графики на экран во время исполнения посредством рисования прямо в поле формы.

Я надеюсь, вы кое-что усвоили о графической подсистеме CBuilder. В следующей главе мы поглубже исследуем VCL (Visual Component Library, библиотека визуальных компонентов)

CBuilder и приоткроем новые тайны компонентов.

Borland C++ Builder (+CD). Библиотека программиста 59

Глава 4. Компоненты и обработчики событий компонентов

Обзор VCL

Динамические компоненты

Использование компонентов

Drag-And-Drop

Обработка сообщений

Ручная прорисовка

Сердцем системы CBuilder является Visual Component Library (библиотека визуальных компонентов), или VCL, которая является как бы набором строительных блоков, включающим в себя такие компоненты, как формы (TForm), поля ввода (TEdit), списки (TListBox), управляющие элементы ActiveX, стандартные диалоги открытия и сохранения файлов. В предыдущих главах мы затронули некоторые компоненты VCL и их использование. Эта глава головокружительное путешествие по VCL и обзор того, как VCL помогает решить каждодневные проблемы у программиста на CBuilder.

Эта глава не является руководством по VCL, так как компания Borland предоставляет вполне приличное руководство в документации к CBuilder. Вместо этого данная глава поэтапный анализ того, как с помощью VCL решать стандартные программистские проблемы. Вот неполный список того, о чем мы будем говорить в этой главе:

создание компонентов во время работы приложения (runtime);

перетаскивание чего-либо между компонентами;

проверка правильности данных в поле ввода;

работа с индикаторами прогресса задания;

пользовательская прорисовка компонентов.

Большинство книг по CBuilder предоставляет вам детальнейшие примеры использования VCL, но немногие применяют VCL к реальным проблемам программирования. В этой главе мы начнем исследовать невероятно мощную модель программирования в CBuilder, а также что это дает вам, как профессиональному программисту. CBuilder отличается маленькими штрихами, теми изюминками, которых пользователь вправе ожидать от высококачественных программ. Теперь, когда вам предстоит работать с CBuilder, нет никаких оправданий в неиспользовании этих

дополнительных возможностей в ваших программах для максимального упрощения работы с ними конечного пользователя.

Краткий обзор с точки зрения программиста

С вашей точки зрения как программиста, VCL не является иерархией. Это набор независимых строительных блоков, которые вы перетаскиваете на форму, устанавливаете их свойства и используете там, где, как вам кажется, они подходят. С компонентами VCL можно обращаться индивидуально, почти или совсем не заботясь о том, как они вписываются в общую схему. Только разработчики новых компонентов действительно обязаны задавать себе вопросы как? и почему? в отношении VCL. Остальным из нас можно сидеть спокойно и наслаждаться блеском легко сделанных приложений, которые используют в себе строительные блоки VCL.

Тем не менее, есть некий смысл в системе компонентов, а знание иерархии системы может помочь вам в использовании некоторых из них. Давайте заглянем на важные уровни архитектуры компонентов перед тем, как погрузиться в толщу VCL в нашем первом примере.

Первый и самый важный класс в иерархии VCL — это TObject. TObject — предок всех классов VCL, он содержит некоторые важнейшие для всех классов методы. Тем не менее, TObject не

Borland C++ Builder (+CD). Библиотека программиста 60

содержит никаких собственных свойств. Посмотрев на список методов класса TObject, вы найдете метод ClassName, который возвращает настоящее имя класса объекта. Если у вас есть указатель на объект базового класса, который в действительности является объектом порожденного (наследующего) класса, то вы можете использовать метод TObject::ClassName , чтобы выяснить, к какому классу он принадлежит на самом деле. Точно так же, метод ClassParent вернет вам непосредственного родителя данного класса. Используя метод ClassParent, вы можете прогуляться по дереву классов обратно до уровня TObject , начиная с любого объекта VCL в системе. С точки зрения того, как что работает, на уровне TObject самым важным является метод Dispatch . Метод Dispatch отвечает за передачу сообщений в обработчики событий, определенные для объекта. Используя Dispatch , вы можете передать объекту любое сообщение и быть уверенным, что если объект поддерживает обработку данного сообщения, то оно будет доставлено к соответствующему обработчику.

Следующим по важности после TObject является класс TControl , который является базой для всех управляющих элементов (controls) в системе VCL, например форм, полей ввода, окон диалога. TControl относится только к видимым компонентам в системе. Видимым (визуальным) компонентом является тот, который пользователь может увидеть и которым он может управлять во время работы приложения. Это значит, что компоненты TTimer (таймер) и TDatabase (компонент база данных) не являются наследниками класса TControl , а вот TOpenDialog (стандартный диалог открытия файлов), хотя он и не виден во время дизайна приложения, является его наследником. Так как TControl поддерживает визуальные компоненты, то в классе TControl вы найдете основные визуальные свойства управляющих элементов. Вы найдете здесь такие свойства, как Height (высота), Width (ширина), ClientHeight (высота клиентской области), ClientWidth (ширина клиентской области), Hint (подсказка), Cursor (тип курсора) и другие. Методами класса являются Hide (убрать), Show (показать), Update (обновить) и Perform (исполнять). С точки зрения «как что работает» в классе TControl есть метод WndProc, который обрабатывает специфические сообщения. Если вам нужно изменить стандартную обработку сообщений для управляющего элемента, смотрите метод WndProc .

Наконец, еще одним важным классом является TComponent, который представляет все компоненты в системе. Класс TComponent предоставляет средства для добавления компонента в палитру компонентов в интегрированной среде, возможности компонента содержать другие компоненты и управлять ими, а также возможности работы с потоками (streams) <$FНе путать с потоками параллельными процессами (threads). — Примеч. перев.>и файлами. TComponent включает в себя все компоненты, видимые или нет во время работы приложения. Важными свойствами класса TComponent являются ComponentCount (которое показывает количество компонентов, которые содержит данный компонент), Components (содержит указатели на все компоненты, содержащиеся в данном) и ComponentState (описывает текущее состояние компонента). Свойство ComponentState важно из-за флага csDesigning, который указывает, что компонент находится в стадии дизайна.

Такова, в двух словах, основа системы VCL. Вместо того, чтобы долго и нудно говорить о том, как использовать библиотеку VCL и что вы можете сделать с ее помощью, давайте сразу прыгнем в омут и примемся писать примеры некоторых приложений для того, чтобы показать мощь VCL в действии. В нашем первом примере мы занимаемся проблемой динамического создания компонентов VCL «на ходу», то есть во время работы приложения.

Динамические компоненты, часть первая

Если бы вы заглянули в эхоконференции (newsgroups) по CBuilder в Internet, то вы увидели бы, что один из наиболее частых вопросов это как создавать управляющие элементы во время работы приложения. Хотя CBuilder и предоставляет богатый ассортимент компонентов и возможности легкого манипулирования ими в дизайнере форм, многие программисты не хотят связываться со

Соседние файлы в предмете Программирование на C++