«ПРОГРАММИРОВАНИЕ В С++ BUILDER Учебное пособие по курсу МЕТОДЫ ПРОГРАММИРОВАНИЯ для студентов специальностей G31 03 01 Математика, G31 03 03 Механика Минск 2007 УДК 004.43(075.8) ББК 32.973.26-018.1я73 Б69 Авторы: В. С. ...»
БЕЛОРУССКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ
МЕХАНИКО-МАТЕМАТИЧЕСКИЙ ФАКУЛЬТЕТ
Кафедра численных методов и программирования
ПРОГРАММИРОВАНИЕ В С++
BUILDER
Учебное пособие
по курсу «МЕТОДЫ ПРОГРАММИРОВАНИЯ»
для студентов специальностей
G31 03 01 «Математика», G31 03 03 «Механика»
Минск 2007 УДК 004.43(075.8) ББК 32.973.26-018.1я73 Б69 Авторы:
В. С. Романчик, А. Е. Люлькин Рецензенты:
старший преподаватель Н. А. Аленский, кандидат физико-математических наук, доцент, зав. кафедрой информационных технологий БГУК П. В. Гляков кандидат физико-математических наук, доцент С. В. Суздаль Рекомендовано Ученым советом механико-математического факультета БГУ 2006 года, протокол № _ Программирование в C++ BUILDER: учебное пособие для студ. механико-матем. фак. / В. С. Романчик, А.Е.Люлькин. Мн.: БГУ, 2007. –126 с.
ISBN 985-485-498-1.
В пособии рассматриваются вопросы, относящиеся к использованию технологии объектно-ориентированного программирования в системе C++ Builder. Описание методологии построения классов и использования компонентов сопровождается многочисленными примерами.
Предназначено для студентов 2-го курса механико-математического факультета, изучающих курс «Методы программирования».
УДК 004.43(075.8) ББК 32.973.26-018.1я ©Романчик В.С., Люлькин А.Е.
ISBN 985-485-498-1 БГУ,
ВВЕДЕНИЕ
Основные характеристики С++Builder. C++Builder включает язык C++, компилятор, интегрированную среду разработки приложений IDE (Integrated Development Environment), отладчик и различные инструменты. C++Builder содержит комплект общих элементов управления, доступ к Windows API, библиотеку визуальных компонентов VCL (Visual Component Library), компоненты и инструменты для работы с базами данных.C++Builder добавляет к процессу программирования на языке C++ возможность быстрой визуальной разработки интерфейса приложений.
Кроме библиотек OWL (Object Windows Library) и MFC (Microsoft Foundation Classes), он использует библиотеку VCL и позволяет включить в форму диалоги с пользователем, оставляя разработчику для реализации только функциональную часть, воплощающую алгоритм решения задачи.
C++Builder имеет общую с Delphi библиотеку классов, часть из которых осталась написанной на языке Object Pascal. Благодаря этому, а также включению в С++Builder компиляторов С++ и Object Pascal, в приложениях можно использовать компоненты и код, написанные на Object Pascal, а также формы и модули Delphi.
Компоненты C++Builder. Создание пользовательского интерфейса приложения заключается в добавлении в окно формы объектов, называемых компонентами. C++Builder позволяет разработчику создавать собственные компоненты и настраивать Палитру компонентов.
Компоненты разделяются на видимые (визуальные) и невидимые (невизуальные). Визуальные компоненты появляются как во время выполнения, так и во время проектирования. Невизуальные компоненты появляются во время проектирования как пиктограммы на форме. Они не видны во время выполнения, но обладают функциональностью. Для добавления компонента в форму можно выбрать мышью нужный компонент в Палитре компонентов и щелкнуть левой клавишей мыши в нужном месте проектируемой формы. Компонент появится на форме, и далее его можно перемещать и изменять. Каждый компонент C++ Builder имеет три характеристики: свойства, события и методы. Инспектор объектов автоматически показывает свойства и события, которые могут быть использованы с компонентом. Свойства являются атрибутами компонента, определяющими его внешний вид и поведение. Инспектор объектов отображает опубликованные (published) свойства компонентов на странице свойств (properties) и используется для установки publishedсвойств во время проектирования. Для изменения свойств компонента во время выполнения приложения нужно добавить соответствующий код.
Помимо published-свойств, компоненты могут иметь открытые (public) свойства, которые доступны только во время выполнения приложения.
События. Страница событий (Events) Инспектора объектов показывает список событий, распознаваемых компонентом и возникающих при изменении состояния компонента. Каждый экземпляр компонента имеет свой собственный набор функций - обработчиков событий. Создавая обработчик события, вы поручаете программе выполнить указанную функцию, если это событие произойдет. Чтобы добавить обработчик события, нужно выбрать компонент, затем открыть страницу событий Инспектора объектов и дважды щелкнуть левой клавишей мыши рядом с событием. Это заставит C++ Builder сгенерировать текст пустой функции с курсором в том месте, где следует вводить код. Далее нужно ввести код, который должен выполняться при наступлении данного события.
Среда разработки (IDE). C++ Builder представляет собой приложение, главное окно которого содержит меню (сверху), инструментальную панель (слева) и Палитру компонентов (справа).
Помимо этого при запуске C++ Builder появляются окно Инспектора объектов и окно Object TreeView (слева), а также форма нового приложения (справа). Под окном формы приложения находится окно Редактора кода.
Рис. 2. Главное окно интегрированной среды разработки Создание приложений в С++Builder. Первым шагом в разработке приложения C++ Builder является создание проекта. Чтобы создать новый проект, нужно выбрать пункт меню File|New| Application.
C++ Builder создает файл Project.bpr, а также головной файл проекта Project.cpp, содержащий функцию WinMain(). Функция WinMain() в Windows-приложениях используется вместо функции main(). При добавление новой формы C++ Builder обновляет файл проекта и создает следующие дополнительные файлы:
• файл формы с расширением.dfm, содержащий информацию о форме;
• файл модуля с расширением.cpp, содержащий код на C++;
• заголовочный файл с расширением.h, содержащий описание класса формы.
Для того чтобы откомпилировать текущий проект, нужно выбрать пункт меню Compile. Для того чтобы откомпилировать проект и создать исполняемый файл, из меню Run нужно выбрать пункт Run. В результате выполнения будет получена следующая форма:
Структура файла проекта. Для каждого приложения C++Builder создается xml-файл проекта Project.bpr и файл ресурсов. Еще один файл головной файл проекта, содержащий функцию WinMain(), генерируется при выборе пункта меню File|New Application. Первоначально по умолчанию этому файлу присваивается имя Project1.cpp. Если в процессе разработки приложения добавляются формы и модули, C++Builder обновляет файл. Для просмотра файла следует выбрать пункт меню Project|View Source.
В головном файле проекта имеется определенный набор ключевых элементов:
• Директива препроцессора #include предназначена для включения заголовочного файла, ссылающегося на описания классов библиотеки VCL.
• Директива #pragma hdrstop предназначена для ограничения списка заголовочных файлов, доступных для предварительной компиляции.
• Директива USEFORM показывает модули и формы используемые в проекте.
• Директива USERES компилятора присоединяет файлы ресурсов к выполняемому файлу. При создании проекта автоматически создается файл ресурсов .res для хранения курсоров, пиктограмм и других ресурсов.
• Application->Initialize(). Это утверждение инициализирует приложение.
• Application->CreateForm(). Это утверждение создает форму приложения. Каждая форма в приложении имеет свое утверждение CreateForm.
• Application->Run(). Это утверждение запускает приложение.
• Блок try...catch используется для корректного завершения приложения в случае возникновения ошибки.
Типичный головной файл проекта имеет следующий вид:
//Project1.cpp -------------------------------------------------------include #pragma hdrstop USERES("Project1.res");
USEFORM("Unit1.cpp", Form1);
//--------------------------------------------------------------------------WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) Application->CreateForm(classid(TForm1), &Form1);
catch (Exception &exception) { Application->ShowException(&exception); } Структура файла Project1.bpr. Файл Project1.bpr представляет XML-проект (C++Builder XML Project), содержащий описание создаваемого приложения. Это текстовый файл, содержащий указания на то, какие файлы должны компилироваться и компоноваться в проект, а также пути к используемым каталогам.
Структура модуля. Модуль содержит реализацию функциональной части объекта на языке C++ и по умолчанию представляет собой файл Unit1.cpp. Каждый такой файл компилируется в объектный файл с расширением.obj. При добавлении к проекту новой формы генерируется новый модуль.
Имя исходного файла модуля и файла формы (*.dfm) должны быть одинаковыми. При создании обработчика событий в тексте модуля генерируется шаблон функции обработчика события, в который вводится код, выполняемый при наступлении обрабатываемого события.
Ниже приводится текст модуля, генерируемый для исходной формы:
//Unit1.cpp -----------------------------------------------------------include #pragma hdrstop #include "Unit1.h" #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1;//указатель на объект //--------------------------------------------------------------------------fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //реализация конструктора Структура заголовочного файла. Заголовочный файл (файл с расширением.h, по умолчанию Unit1.h) генерируется при создании нового модуля и содержит описание класса формы. Такие описания генерируются автоматически и изменяются при внесении в форму новых компонентов или генерации новых обработчиков событий. В заголовочном файле содержится интерфейс, а в самом модуле – реализация методов.
При удалении из формы компонентов их описания удаляются из заголовочного файла. При переименовании компонентов изменяются их описания в заголовочном файле, а также имена и описания обработчиков событий. Однако при этом не изменяются ссылки на эти компоненты и обработчики событий, используемые в других функциях. В связи с этим рекомендуется переименовывать компоненты и обработчики событий сразу же после их создания, пока на них не появились ссылки.
В модуле могут содержаться классы и функции, не описанные в заголовочном файле, однако видимость их в этом случае ограничивается данным модулем.
Ниже приводится заголовочный файл для исходной формы:
//Unit1.h--------------------------------------------------------------------------ifndef Unit1H #define Unit1H //--------------------------------------------------------------------------include #include #include #include class TForm1 : public TForm published: // IDE-managed Components private: // User declarations fastcall TForm1(TComponent* Owner);
//--------------------------------------------------------------------------extern PACKAGE TForm1 *Form1;
#endif //--------------------------------------------------------------------------Файл формы. Форма является одним из важнейших элементов приложения C++ Builder. Процесс редактирования формы происходит при добавлении к форме компонентов, изменении их свойств, создании обработчиков событий. Когда к проекту добавляется новая форма, создаются три отдельных файла: 1) файл модуля (*.cpp) содержит код методов, связанных с формой; 2) заголовочный файл (*.h) содержит описание класса формы; 3) файл формы (*.dfm) содержит сведения об опубликованных (доступных в Инспекторе объектов) свойствах компонентов, содержащихся в форме.
При добавлении компонента к форме заголовочный файл и файл формы модифицируются. При редактировании свойств компонента в Инспекторе объектов эти изменения сохраняются в файле формы.
Хотя в C++ Builder файл *.dfm сохраняется в двоичном формате, его содержание можно просмотреть с помощью редактора кода. Для этого нужно нажать правую клавишу мыши над формой и из контекстного меню формы выбрать пункт View as Text.
Ниже приводится листинг файла некоторой формы:
//Unit1.dfm--------------------------------------------------------------------------object Form1: TForm Caption = 'Form1' Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = - Font.Name = 'MS Sans Serif' Font.Style = [] OldCreateOrder = False PixelsPerInch = TextHeight = Простейшее приложение. Важнейшей особенностью C++Builder является автоматическая генерация кода программы. Когда к форме добавляете компонент, в тексте файла Unit1.h появляется объявление объекта класса данного компонента. Например, перенос на пустую форму компонента кнопки TButton сгенерирует объявление объекта Button1, а определение события OnClick – объявление метода ButtonlClick, являющегося обработчиком этого события.
Рассмотрим простейшее приложение. Создается форма для сложения двух чисел. Используются компоненты: TEdit – для ввода чисел и отображения результата; TLabel – для вывода строк “+” и “=”; TButton – кнопка, связанная с событием OnClick, для сложения чисел.
Будем использовать следующие свойства и методы для компонентов:
TEdit: Name (имя объекта), ReadOnly (режим "только чтение"), Text (текстовое поле);
TLabel: Caption (текстовое поле);
TButton: Caption (надпись на кнопке), OnClick (событие типа нажатия кнопки).
На рис. 4 приводятся окна Инспектора объектов в процессе работы со свойствами компонентов.
Двойной щелчок по компоненту Button1 приведет к выводу окна редактирования для Unit1.cpp и предложит определить тело метода Button1Click(), которое для рассматриваемой задачи должно иметь вид:
void fastcall TForm1::Button1Click(TObject *Sender) Edit3->Text=IntToStr(StrToInt(Edit1->Text)+StrToInt(Edit2->Text));
Для преобразования строки в целое число и обратно используются функции StrToInt() и IntToStr(), соответственно. На рис. 5 показан результат выполнения приложения.
Рис. 5. Форма в процессе выполнения приложения Рассмотрим основные инструменты визуальной разработки приложений.
Администратор проектов. Предназначен для манипуляций с текущим проектным файлом с расширением.срр. Чтобы открыть окно администратора, выполните команду View|Project|Manager.
Редактор форм. Форма представляет объект, отображаемый в виде окна с управляющими компонентами. C++Builder создает форму в окне Редактора при добавлении формы к проекту или берет ее из Хранилища объектов.
Инспектор объектов. Инспектор объектов используется при проектировании объектов и методов и имеет две вкладки: Свойства (Properties) и События (Events).
Просмотрщик объектов Object TreeView позволяет просматривать дерево объектов.
Хранилище объектов. Обеспечивает возможность разделения (sharing) или повторного использования (reuse) содержащихся в нем объектов. В качестве объектов хранения могут выступать созданные пользователем формы, проекты, модули данных.
Редактор кода. Предоставляет средство для просмотра и редактирования текста программного модуля (Unit).
Палитра компонентов. C++Builder поставляется вместе с Библиотекой Визуальных Компонентов VCL (Visual Component Library), содержащей множество повторно используемых компонентов. C++Builder позволяет не только пользоваться готовыми компонентами, но и создавать новые компоненты.
Формы и диалоговые окна. Главная форма (Form1) открывается при открытии приложения. Метод Close() закрывает форму и прекращает выполнение приложения. Свойства формы могут устанавливаться как на этапе проектирования, так и на этапе выполнения приложения. Форма используется в качестве контейнера для размещения других компонентов. В табл. 1 приведены некоторые свойства формы.
С формой связаны события: OnActivate – вызывается при активизации формы после получения фокуса ввода; OnCreate –вызывается при создании формы; OnClose – вызывается при закрытии формы; OnClick – вызывается при щелчке кнопки; OnKeyDown, OnKePress –первое событие активизируется при нажатии любой клавиши, в том числе функциональной, для второго события клавиша не должна быть функциональной;
OnMouseDown, OnMouseMove и другие – связаны с мышью. При возникновении каждого события вызывается соответствующй методобработчик события. Например:
void fastcall TForm1::FormClick(TObject *Sender){ } Обработчику события передается указатель на вызвавший его объект.
Дополнительными параметрами могут быть, например, текущие координаты указателя мыши и т.п.
Форма создается при создании приложения. Приложение может содержать несколько форм. Существует два вида оконных приложений:
SDI и MDI. SDI-приложения могут отображать несколько окон, не привязанных к главному. Каждая форма может отображаться в модальном режиме и требовать закрытия при переходе к другой форме (Form2->ShowModal(), Form2->Close()). Пока модальное окно не закрыто, нельзя перейти к другому окну. В немодальном режиме разрешается доступ к нескольким формам. В качестве примера рассмотрим приложение, состоящее из двух немодальных SDI форм: Form1 и Form3. Создадим кнопку и запишем код обработчика события void fastcall TForm1::Button1Click(TObject *Sender) Form3->Show();
При этом необходимо включить в заголовочный файл Unit1.h первой формы заголовочный файл второй формы: #include "Unit3.h" На рис. 6 показан результат выполнения приложения.
Рассмотрим еще одно приложение состоящее из двух форм: Form1 и AboutBox. Для этого в меню выберем File|New|Forms|AboutBox. В свойствах формы AboutBox выберем: Visible=true и FormStyle=fsStayOnTop.
Присваивается значение обекта, на который устанавливается ActiveControl фокус ввода при загрузке формы Cursor Вид рамки объекта. Принимает значения: bsNone (нет рамки);
bsSingle (простая рамка); bsSizeable (рамка, позволяющая изменять размеры объекта мышью); bsDialog (рамка в стиле диалоговых BorderStyle окон); bsToolWindow (как bsSingle, но с небольшим заголовком);
bsSizeToolWin (как bsSizeable, но с небольшим заголовком) Заголовок. Для одних объектов применяется, чтобы задать заголовок в окне или надпись на кнопке, для других – описывает Caption Содержит четыре подсвойства, определяющие минимальный и Constraints максимальный допустимый размер объекта Определяет, будет ли происходить для данного объекта событие OnClick, когда пользователь нажмет клавишу Enter (для этого Default свойство Default должно иметь значение true) Определяет, можно ли объект произвольно перетаскивать по окну (dkDrag) или же его можно перемещать как стыкуемый объект DragKind (dkDock), который сам определяет свою форму при стыковке с Определяет доступность объекта. Когда свойство Enabled имеет Enabled значение false, объект становится недоступным для пользователя Определяет шрифт, которым будут делаться все надписи внутри Font объекта. Содержит множество подсвойств Height Текст подсказки, которая всплывает при наведении указателя мыши на объект. Эта подсказка будет показываться, если свойство Hint Name Контекстное меню, связанное с объектом и вызываемое при щелчке правой кнопки мыши над этим объектом. Выбирается в PopupMenu Если имеет значение true, то учитывается свойство PixelsPerlnch Scaled Определяет, надо ли показывать всплывающую подсказку, ShowHint хранящуюся в свойстве Hint Верхняя координата объекта на компоненте-родителе Top Определяет, будет ли видим объект во время работы программы Visible (по умолчанию – false) Width Рис. 6. Результат вывода формы в немодальном режиме В результате получим следующий головной файл проекта:
USERES("Project2.res");
USEFORM("Unit1.cpp", Form1);
USEFORM("Unit2.cpp", AboutBox);
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int){ Application->Initialize();
Application->CreateForm(classid(TForm1), &Form1);
Application->CreateForm(classid(TAboutBox), &AboutBox);
catch (Exception &exception) Application->ShowException(&exception);
Закроем форму AboutBox по нажатию кнопки OK. Для этого запишем следующий обработчик события для кнопки:
void fastcall TAboutBox::OKButtonClick(TObject *Sender) Close();
На рис. 7 показан результат выполнения приложения.
Для создания MDI приложения надо поменять свойство формы FormStyle с fsNormal на fsMDIForm. После этого новая подчиненная форма создается вместе с изменением свойства FormStyle на fsMDIChild.
При попытке закрытия подчиненная форма сворачивается. Чтобы закрыть подчиненную форму, можно в обработчике события FormClose набрать: Action=caFree;.
Диалоговые окна. Кроме оконных форм для вывода и ввода строк можно использовать диалоговые окна. Первое окно, используемое для вывода вызывается функцией ShowMessage():
void fastcall TForm1::FormClick(TObject *Sender) ShowMessage("Вывод в окно ShowMessage");
Это окно открывается модально.
Второе окно, используемое как для вывода, так и для ввода, вызывается функцией InputBox():
void fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift) String Name=InputBox("InputBox","What is your name","");
Результаты выполнения этих функций приведены на рис. 8.
Окна для ввода и вывода можно также вызывать с помощью методов InputQuery("str1","str2", inputstr) и MessageDlg(str, mtInformation, TMsgDlgButtons() WindowState = wsMaximized;
Form1->WindowState = wsMinimized;
Form1->WindowState = wsNormal;
1. Создать приложение, состоящее из модальной и SDI форм. Создать кнопки для закрытия этих форм. Изменить свойства форм.
2. Создать приложение, состоящее из формы AboutBox и двух MDI форм. Создать кнопки для закрытия этих форм. Изменить свойства форм.
3. Создать форму и написать несколько обработчиков событий, связанных с созданием и изменением свойств формы.
4. Из окна редактирования (Edit) ввести символьную строку и преобразовать в целое и вещественное числа. При преобразовании в число предусмотреть обработку исключительной ситуации 5. Разработать калькулятор, реализующий арифметические операции и операции со стандартными функциями.
6. Разработать калькулятор для перевода чисел в двоичную, восьмеричную и шестнадцатеричную системы счисления и реализовать основные операции.
7. Разработать калькулятор для работы с комплексными числами и реализовать основные операции.
1. C++BUILDER И ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ
ПРОГРАММИРОВАНИЕ
C++Builder использует понятие компонентов – специальных классов, содержащих, кроме обычных данных-членов класса и методов, также свойства и события. Свойства и события позволяют манипулировать видом и функциональным поведением компонентов как на стадии проектирования приложения, так и во время его выполнения.Свойства (properties) компонентов представляют собой расширение понятия данных-членов и используют ключевое слово property для объявления. При помощи событий (events) компонент сообщает пользователю о том, что на нее оказано некоторое воздействие. Обработчики событий (event handlers) представляют собой методы, реализующие реакцию программы на возникновение событий. Типичные события – нажатие кнопки или клавиши на клавиатуре. Компоненты имеют ряд особенностей:
• Все компоненты являются прямыми или косвенными потомками класса TComponent. При этом иерархия наследования следующая: Tobject->Tpersistent-> Tcomponent->Tcontrol->….
• Компоненты используются непосредственно, они не могут служить базовыми классами для построения новых подклассов.
• Компоненты размещаются только в динамической памяти с помощью оператора new.
• Компоненты можно добавлять к Палитре компонентов и манипулировать с ними посредством Редактора форм.
C++Builder дает возможность объявить базовый класс, который инкапсулирует имена свойств, данных, методов и событий. Каждое объявление внутри класса определяет привилегию доступа к именам класса в зависимости от того, в каком разделе имя появляется. Каждый раздел начинается с одного из ключевых слов: private, protected и public, определяющих возможности доступа к элементам соответствующего раздела.
Рассмотрим пример объявления класса. Отметим объявление свойства Count в защищенном разделе, а метода SetCount, реализующего запись в данное Fcount, – в закрытом разделе.
class TPoint { private:
int FCount; // Закрытый член данных void fastcall SetCount(int Value);
protected:
property int Count = // Защищенное свойство { read= FCount, write=SetCount };
double x; // Защищенный член данных double у; // Защищенный член данных public:
TPoint(double xVal, double yVal); // Конструктор double getX();
double getY();
Объявления класса и определения методов обычно хранятся в разных файлах (с расширениями.h и.срр, соответственно). Следующий пример показывает, если методы определяются вне класса, то их имена следует уточнять с помощью имени класса.
TPoint::TPoint(double xVal, double yVal) { // Тело конструктора void fastcall TPoint::SetCount( int Value ){ if ( Value != FCount ) FCount = Value; // Запись нового значения Update(); // Вызов метода Update double TPoint::getX(){ // Тело метода getX(), объявленного в классе TPoint C++Builder дает возможность объявить производный класс, который наследует свойства, данные, методы и события всех своих предшественников в иерархии классов, а также может объявлять новые характеристики и перегружать некоторые из наследуемых функций. Объявление производного класса можно выполнить следующим образом:
class derivedClass : [] parentClass { private:
protected:
public:
published:
Отметим появление нового раздела с ключевым словом published – дополнение, которое C++Builder вводит в стандарт ANSI C++ для объявления опубликованных элементов компонентных классов. Этот раздел отличается от раздела public только тем, что компилятор генерирует информацию RTTI (информация времени выполнения) о свойствах, данных-членах и методах объекта и C++Builder организует передачу этой информации Инспектору объектов.
Когда класс порождается от базового, все имена базового класса в производном классе автоматически становятся закрытыми по умолчанию (если спецификатор доступа при наследовании не указывается). Но это можно изменить, указав следующие спецификаторы доступа при наследовании базового класса:
• protected. Наследуемые (т.е. защищенные и открытые) имена базового класса становятся защищенными в экземплярах производного класса.
• public. Открытые имена базового класса и его предшественников будут открытыми в экземплярах производного класса, а все защищенные останутся защищенными.
Рассмотрим применение методики расширения и ограничения характеристик на примере создания разновидностей кнопки при наследовании базового компонента TButtonControl из Библиотеки Визуальных Компонентов. Базовый класс TButtonControl способен с помощью родительского метода Draw отображать кнопку в виде двух вложенных прямоугольников: внешней рамки и внутренней закрашенной области. Чтобы создать простую кнопку без рамки, нужно построить производный класс SimpleButton, использовав в качестве родительского TButtonControl, и перегрузить метод Draw:
class SimpleButton: public TButtonControl { public:
SimpleButton(int x, int y) ;
void Draw() ;
~SimpleButton() { } SimpleButton::SimpleButton(int x, int y) : TButtonControl(x, y) void SimpleButton::Draw() {outline->Draw();} Единственная задача конструктора объекта для SimpleButton – вызвать конструктор базового класса с двумя параметрами. Именно переопределение метода SimpleButton:: Draw () предотвращает вывод обводящей рамки кнопки (как происходит в родительском классе). Чтобы изменить код метода, надо изучить его по исходному тексту базового компонента TButtonControl.
Создадим кнопку с пояснительным названием. Для этого нужно построить производный класс TextButton из базового TButtonControl и перегрузить метод Draw с расширением его функциональности:
class Text {//Вспомогательный класс public:
Text(int x, int y, char* string) { } void Draw() { } class TextButton: public TButtonControl { Text* title;
public:
TextButton(int x, int y, char* title);
void Draw();
~TextButton() { } TextButton::TextButton(int x, int y, char* caption):
TButtonControl(x, y) { title = new Text(x, y, caption);
void TextButton::Draw () { TButtonControl::Draw() ;
title->Draw() ;
1.4. Идентификация типов времени выполнения RTTI Идентификация типов при выполнении программы RTTI (Run-Time Туре Identification) позволяет вам написать переносимую программу, которая способна определять фактический тип объекта в момент выполнения даже в том случае, если программе доступен только указатель на этот объект. Это дает возможность, например, преобразовывать тип указателя на базовый класс в указатель на производный тип фактического объекта данного класса. Таким образом, преобразование типов может происходить не только статически – на фазе компиляции, но и динамически – в процессе выполнения. Динамическое преобразование указателя в заданный тип осуществляется с помощью оператора dynamic_cast.
Механизм RTTI также позволяет проверять, имеет ли объект некоторый определенный тип, или принадлежат ли два объекта одному и тому же типу. Оператор typeid определяет фактический тип аргумента и возвращает указатель на объект класса typeinfo, который этот тип описывает.
Передавая RTTI Инспектору объектов во время выполнения, C++Builder информирует его о типах свойств и членов данного класса.
Пакеты – это особый тип динамических библиотек DLL для Windows.
Как и обычные DLL, пакетные файлы BPL (Borland Package Library) содержат код, разделяемый многими приложениями. C++Builder размещает наиболее часто используемые компоненты в пакете под названием VCL50.BPL. При создании исполняемого кода приложения в нем остаются только уникальные инструкции и данные, а разделяемый код подгружается из указанных пакетов во время исполнения.
Объявление нового компонентного класса в интерфейсном модуле должно включать предопределенный макрос PACKAGE сразу же за ключевым словом class:
class PACKAGE MyComponent :...
Этот же макрос должен присутствовать в кодовом модуле там, где объявлена функция регистрации компонента:
void fastcall PACKAGE Register(){…} Образующийся при подстановке макроса PACKAGE код обеспечивает возможность импортирования и экспортирования объявленного компонентного класса в результирующий файл с расширением BPL. Если при создании нового компонента вы пользуетесь мастером (по команде Component | New Component), C++Builder автоматически вводит PACKAGE в нужное место.
1.6. Объявления компонентных классов Опережающие объявления классов Библиотеки Визуальных Компонентов VCL, входящей в состав C++Builder, используют модификатор _declspec:
_declspec() Это ключевое слово может появляться в любом месте перечня объявлений, причем спецификатор принимает одно из следующих значений:
delphiclass используется для опережающего объявления прямых или косвенных производных от VCL-класса TObject;
delphireturn используется для опережающего объявления прямых или косвенных производных от VCL-классов Currency, AnsiString, Variant, TDateTime и Set. Он определяет правила совместимости VCL при обращении с параметрами и возвращаемыми значениями функцийчленов.
pascalimplementation указывает, что компонентный класс реализован на Объектном Паскале.
C++BuiIder использует модификатор _property для объявления свойств компонентных классов. Синтаксис описания свойства имеет вид:
_property = {} ;
Список атрибутов содержит перечисление следующих атрибутов свойства:
write = < член данных или метод записи > – определяет способ присваивания значения члену данных;
read = < член данных или метод чтения > – определяет способ получения значения члена данных;
default = < булева константа > – разрешает или запрещает сохранение значения свойства по умолчанию в файле формы *.dfm;
stored = < булева константа или функция > – определяет способ сохранения значения свойства в файле формы с расширением *.dfm.
C++BuiIder использует модификатор published для спецификации тех свойств компонентов, которые будут отображаться Инспектором объектов на стадии проектирования приложения. Правила видимости, определяемые этим ключевым словом, не отличаются от правил видимости членов данных, методов и свойств, объявленных как public.
1.8. Объявления обработчиков событий C++Builder использует модификатор _closure для объявления функций обработчиков событий:
(_closure * ) () Это ключевое слово определяет указатель функции с именем name. В отличие от 4-байтового указателя обычной функции, 8-байтовый указатель _closure передает еще и скрытый указатель this на экземпляр класса.
Введение 8-байтовых указателей делает возможным вызывать некоторую функцию определенного класса, а также обращаться к функции в определенном экземпляре этого класса.
Любой компонент может находиться во владении (ownership) других компонентов. Свойство компонента Owner (Владелец) содержит ссылку на компонент, который им владеет. Владелец отвечает за освобождение тех компонентов, которыми владеет, когда сам разрушается. Так, в процессе конструирования формы она автоматически становится владельцем всех компонентов, размещенных на ней, даже если часть их размещена на другом компоненте, например, таком как TPanel. Владение применимо не только к видимым, но и к невидимым (TTimer, TDataSource) компонентам.
Когда компонент не переносится на форму, а создается динамически в процессе выполнения программы, конструктору компонента передается ее владелец в качестве параметра. В следующем примере неявный владелец (например, форма) передается конструктору компонента TButton как параметр. Конструктор TButton выполнит присваивание значения переданного параметра свойству Owner кнопки MyButton:
MyButton = new TButton(this);
Когда форма, уничтожается, автоматически уничтожается и кнопка MyButton.
Можно создать компонент, у которого нет владельца, передавая значение параметра 0 конструктору компонента. Его уничтожение выполняется с помощью оператора delete.
Свойство Components класса Tcomponent содержит перечень компонентов, которыми владеет данный компонент. Ниже приводится фрагмент кода обработчика события OnClick с циклом отображения имен классов всех компонентов, которыми владеет некоторая форма.
void fastcall TForm1::Button1Click(TObject *Sender) for (int i=0; iClassName()) ;
ShowMessage(Components[i]->ClassParent()->ClassName()) ;
Метод ClassParent() возвращает родительские классы для находящихся на форме классов.
Понятие родительского права (parentship) отличается от права владения и применимо только к видимым компонентам. В качестве родительских компонент могут выступать форма, панель, groupbox, на которых располагаются объекты – потомки. Родительские компоненты обращаются к соответствующим внутренним функциям, чтобы вызвать отображение компонентов-потомков. Родитель также отвечает за освобождение своих потомков, когда сам родитель уничтожается. Свойство компонента Parent (Родитель) содержит ссылку на компонент, который является его родителем. Многие свойства видимых компонентов (например, Left, Width, Top, Height) относятся к родительским элементам управления.
Другие свойства (например, ParentColor и ParentFont) позволяют потомкам использовать свойства родителей.
Компоненту надо присвоить родителя, ответственного за отображение. Это присваивание выполняется автоматически на стадии проектирования при перетаскивании компонента из Палитры компонентов на форму. При создании компонента во время выполнения программы необходимо явно записать это присваивание, иначе компонент не будет отображен:
void fastcall TForm1::FormCreate(TObject *Sender) MyEdit = new TEdit(this); // Передать this как владельца MyEdit->Parent = this; // Передать this как родителя В качестве примера приведем объявление компонента с единственным свойством IsTrue, имеющим значение по умолчанию true, а также конструктор, который устанавливает это значение при инициализации компонентного объекта. Заметим, что если свойство имеет значение по умолчанию false, то не нужно явно устанавливать его в конструкторе, поскольку все объекты (включая компоненты) всегда инициализируют свои члены данных значением 0, т.е. false.
class TMyComponent : public TComponent { private:
Boolean FIsTrue;
public:
fastcall TMyComponent(TComponent* Owner);
published:
property Boolean IsTrue = { read=FIsTrue, write=FIsTrue, default=true };
fastcall TMyComponent:: TMyComponent (TComponent* Owner) : TComponent (Owner) { FIsTrue = true; } 1. Каким образом, и в каких файлах можно создать собственный класс, динамический объект и вызвать методы класса?
2. Что представляют собой свойства компонентов? Приведите примеры нескольких свойств формы.
3. Что представляют собой события компонентов? Приведите примеры.
4. Каким образом можно изменить значения свойств компонентов?
5. Объявить класс MyClass, содержащий некоторую строку, и метод swap(), заменяющий строку другой строкой. Выполнить тестирование.
6. Объявить класс и определить размерность объектов данного класса.
7. Чем отличаются оператор-функции, объявленные как friend, от оператор-функций членов класса C++ и можно ли их использовать в C++Builder?
8. На каком этапе происходит выделение памяти под объекты компонентных классов?
9. Что представляет собой общедоступное свойство класса и его опубликованное свойство?
10. Что поизойдет в результате выполнения следующего кода?
void fastcall TForm1::Button1Click(TObject *Sender) TForm * newForm= new TForm(this);
TButton* button=new TButton(Application);
button->Parent=newForm;
button->Caption="New Button";
button->Left=10;
button->Top=15;
button->Show();
newForm->Caption="newForm";
newForm->ShowModal();
button->Click();
delete newForm;
12. Как распознать нажатые функциональные клавиши?
13. Куда поступают события клавиатуры? Какое свойство формы влияет на то, куда поступают события клавиатуры?
14. В следующем коде какой из объектов отвечает за удаление кнопки?
TButton* B=new TButton(this);
B->Parent=Panel1;
2. КОМПОНЕНТЫ БИБЛИОТЕКИ VCL
Компоненты вкладки Standard Палитры компонентов осуществляют включение в программу стандартных управляющих элементов Windows.Компонент TLabel отображает статический текст, являющийся значением свойства Caption. Например:
Label1->Caption="Это Метка";
Компонент TEdit отображает область редактируемого ввода строки.
Содержимое области редактирования определяется значением свойства Text. Например:
double r1=Form1->Edit1->Text.ToDouble(), y=0, r2=Form1->Edit2->Text.ToDouble();
y=r1+r2;
ShowMessage(FloatToStr(y));
Компонент TButton создает кнопку с надписью. Нажатие на кнопку инициирует некоторое событие. Кнопка, выбранная со значением true свойства Default, запускает обработчик события OnClick для кнопки всякий раз, когда нажимается клавиша Enter в окне диалога. Кнопка прерывания, выбранная со значением true свойства Cancel, запускает обработчик события OnClick для кнопки всякий раз, когда нажимается клавиша Escape.
Ниже приводится пример использования рассматриваемых компонентов Label1-Label4, Edit1-Edit6 и Button1 при решении квадратного уравнения.
//Uni1.cpp #include #pragma hdrstop #include #include "Unit1.h" #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1;
//--------------------------------------------------------------------------fastcall TForm1::TForm1(TComponent* Owner) void fastcall TForm1::Button1Click(TObject *Sender) try{ double a=StrToFloat(Edit1->Text);
double b=StrToFloat(Edit2->Text);
double c=StrToFloat(Edit3->Text);
if(a==0){ShowMessage("Уравнение не квадратное");return;} String str="";
Edit4->Text = str;
Edit5->Text = str;
Edit6->Text=d;
catch(...) {ShowMessage("Input Error");} На рис. 10 показана форма приложения в процессе выполнения.
Можно использовать кнопку с картинкой вместо кнопки типа TButton. Для этого можно применить компонент TBitBtn со вкладки Additional Палитры компонентов.
Компонент ТMеmо отображает прямоугольную область ввода множества строк. Содержимое области редактирования определяет массив строк, являющийся значением свойства Lines. Например:
void fastcall TForm1::Edit1Change(TObject *Sender) { Memo1->Lines->Add(Edit1->Text);
Компонент TСheckBox создает квадратный флажок с двумя состояниями. Состояние check флажка соответствует выбору некоторого варианта (отмечается перечеркиванием квадрата), а состояние uncheck соответствует снятию выбора. При этом свойство компонента Checked меняется с true на false и возникает событие OnClick. Описательный текст флажка хранится в свойстве Caption.
Компонент TRadioButton cоздает круглую кнопку (радиокнопку) с двумя состояниями и описательным текстом. Радиокнопки представляют набор взаимоисключающих вариантов выбора: только одна кнопка может быть выбрана в данный момент времени (отмечается внутренним черным кружком), а с ранее выбранной кнопки выбор автоматически снимается. При нажатии радиокнопки свойство компонента Checked меняется и возникает событие OnClick.
Обычно радиокнопки размещаются внутри предварительно установленного на форме группового контейнера. Если выбрана одна кнопка, выбор всех прочих кнопок в той же группе автоматически снимается. Например, две радиокнопки на форме могут быть выбраны одновременно только в том случае, когда они размещены в разных контейнерах. Если группировка радиокнопок явно не задана, то по умолчанию все они группируются в одном из оконных контейнеров (TForm, TGroupBox или TPanel).
Компонент TListBox отображает прямоугольную область списка текстовых вариантов для выбора, добавления или вычеркивания. Если все элементы списка не умещаются в отведенную область, то список можно просматривать с помощью линейки прокрутки. Элементы списка содержатся в свойстве Items, а номер элемента, который будет выбран во время выполнения программы, – в свойстве ItemIndex. Окно текстового редактора элементов списка открывается кнопкой в графе значений свойства Items. Можно динамически добавлять, вычеркивать, вставлять и перемещать элементы списка с помощью методов Add, Append, Delete и Insert объекта Items. Например:
ListBoxl->Items->Add("Последний элемент списка");
Значение true свойства Sorted устанавливает сортировку элементов списка по алфавиту.
Рассмотрим пример приложения, позволяющего вводить текст в редактируемое поле и добавлять этот текст к списку при нажатии на кнопку. Выберем пункт меню File|New Application для создания проекта и сохраним его главную форму под именем samp1.cpp, а сам проект под именем samp.bpr. Поместим на форму компоненты TButton, TEdit, TRadioButton и TListBox со страницы Standard Палитры компонентов.
После этого выберем на форме компонент Edit1 и удалим текущее значение свойства Text. Затем установим свойство Caption для RadioButton равным "Добавить", для RadioButton2 – "Удалить", для кнопки Button1 – "Выполнить", а для кнопки Button2 – "Выход".
Чтобы добавить обработчик события OnClick для кнопки «Выполнить», нужно выбрать эту кнопку на форме, открыть страницу событий в Инспекторе объектов и дважды щелкнуть мышью на колонке справа от события OnClick. В соответствующей строке ввода появится имя функции. C++ Builder сгенерирует прототип обработчика события и покажет его в редакторе кода. После этого следует ввести следующий код в тело функции:
void fastcall TForm1::Button1Click(TObject *Sender) if(RadioButton1->Checked) if (!(Edit1->Text == "")) ListBox1->Items->Add(Edit1->Text);
if(RadioButton2->Checked){ if (!(ListBox1->ItemIndex == -1)) ListBox1->Items->Delete(ListBox1->ItemIndex);
В обработчик события OnClick для кнопки "Выход" добавим вызов функции Close().
Для компиляции приложения в меню Run выберем пункт Run. На рис. 11 показано окно приложения в процессе выполнения.
редактирования и выпадающего списка текстовых вариантов для выбора.
Значение свойства Text заносится непосредственно в область редактирования. Элементы списка, которые может выбирать пользователь, содержатся в свойстве Items, номер элемента, который будет выбран во время выполнения программы, – в свойстве ItemIndex, а сам выбранный текст – в свойстве SelText. Свойства SelStart и SelLength позволяют установить выборку части текста или обнаружить, какая часть текста выбрана.
Можно динамически добавлять, вычеркивать, вставлять и перемещать элементы списка с помощью методов Add, Append, Delete и Insert объекта Items. Например:
ComboBoxl->Items->Insert(0, "Первый элемент списка");
Компонент TScrollBar создает линейку прокрутки с бегунком для просмотра содержимого окна. Поведение прокручиваемого объекта определяется обработчиком события OnScroll. Значение свойства LargeChange определяет насколько должен продвинуться бегунок, когда пользователь щелкает мышью на самой линейке (по обеим сторонам от бегунка). Значение свойства SmallChange определяет насколько должен продвинуться бегунок, когда пользователь щелкает мышью по кнопкам со стрелками (на концах линейки) или нажимает клавиши позиционирования.
Рассмотрим пример взаимодействия компонентов TScrollBar и TComboBox, которое происходит следующим образом: изменение положения бегунка компонента TScrollBar вызывает изменение соответствующего ему элемента списка компонента TComboBox и наоборот. Ниже приводится листинг программы, содержащий обработчики событий компонентов:
//Unit1.cpp #include #pragma hdrstop #include "desyat.h" //--------------------------------------------------------------------------pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1;
//--------------------------------------------------------------------------fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) //--------------------------------------------------------------------------void fastcall TForm1::ComboBox1Change(TObject *Sender) ScrollBar1->Position=ComboBox1->ItemIndex+1;
//--------------------------------------------------------------------------void fastcall TForm1::ScrollBar1Change(TObject *Sender) ComboBox1->ItemIndex=ScrollBar1->Position-1;
ComboBox1->Items->Add(ScrollBar1->Position-1);
//--------------------------------------------------------------------------На рис. 12 показано окно программы в процессе выполнения.
Компонент TGroupВох создает контейнер в виде прямоугольной рамки, визуально объединяющий на форме логически связанную группу некоторых интерфейсных элементов. Этот компонент представляет собой инкапсуляцию одноименного объекта Windows.
Компонент TRadioGroup создает контейнер в виде прямоугольной рамки, визуально объединяющий на форме группу логически взаимоисключающих радиокнопок.
Радиокнопки образуют группу при помещении их в один и тот же контейнер. Только одна кнопка из данной группы может быть выбрана.
Добавление кнопок к компоненту TRadioGroup выполняется редактированием свойства Items. Присвоение названия радиокнопки очередной строке свойства Items приводит к появлению этой кнопки в группирующей рамке. Значение свойства ItemIndex определяет, какая радиокнопка выбрана в настоящий момент. Можно группировать радиокнопки в несколько столбцов, устанавливая соответствующее значение свойства Columns.
Компонент TPanel создает пустую панель (контейнер), которая может содержать другие компоненты. Можно использовать компонент TPanel для создания на форме панелей инструментов или строк состояния (однако для этого лучше использовать специальные компоненты TToolBar, TPageScroller, TStatusBar).
Компонент TMainMenu создает панель команд главного меню и соответствующие им выпадающие меню для формы.
В качестве примера приведем создание простейшего текстового редактора с возможностью открытия и сохранения текстовых файлов. Будем использовать компоненты TMainMenu, TOpenDialog, TSaveDialog, TMemo. На рис. 13 показан процесс проектирования меню с помощью редактора меню.
Далее приводятся обработчики событий для соответствующих пунктов меню:
void fastcall TForm1::Open1Click(TObject *Sender) // Программная установка свойств компонента OpenDialog OpenDialog1->Options.Clear();
OpenDialog1->Options Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*";
OpenDialog1->FilterIndex = 2;
if (OpenDialog1->Execute()) Memo1->Lines-> LoadFromFile (OpenDialog1->FileName);
//--------------------------------------------------------------------------void fastcall TForm1::Save1Click(TObject *Sender) // Программная установка свойств компонента SaveDialog SaveDialog1->Options.Clear();
SaveDialog1->Options Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*";
SaveDialog1->FilterIndex = 2;
if (SaveDialog1->Execute()) Memo1-> Lines->SaveToFile(SaveDialog1->FileName);
void fastcall TForm1::Exit1Click(TObject *Sender) exit(0);
На рис. 14 показано окно приложения после запуска приложения и открытия в полученном текстовом редакторе файла модуля формы.
Компонент TPopupMenu создает всплывающее меню для формы или для другого компонента. Если необходимо, чтобы специальное меню появлялось при нажатии правой кнопки мыши на элемент, которому приписан данный компонент (свойство PopupMenu содержит имя компонента всплывающего меню), установите значение true свойства AutoPopup. Если всплывающее меню относится к форме, то свойство формы PopupMenu следует установить в имя всплывающего меню, например, PopupMenu1.
Компоненты вкладки Win32 Палитры компонентов осуществляют включение в программу следующих интерфейсных элементов.
Компонент TTabControl отображает набор частично перекрывающих друг друга картотечных вкладок. Названия вкладок вводятся в список свойства Tabs кнопкой в графе значений этого свойства. Если все поля не умещаются на форме в один ряд, то можно установить значение true свойства MultiLine или прокручивать вкладки с помощью кнопок со стрелками. Принципиальное отличие данного компонента от похожего компонента TPageControl состоит в том, что он не имеет множества страниц (панелей). Компонент представляет собой одну страницу с управляющим элементом типа кнопки со многими положениями.
Необходимо написать соответствующие обработчики событий OnChanging и OnChange, чтобы определить, что именно должно происходить на панели при переключениях закладок пользователем.
Компонент TPageControl отображает набор полей, имеющих вид частично перекрывающих друг друга картотечных вкладок (страниц), для организации многостраничного диалога. Многостраничные панели позволяют экономить пространство окна приложения, размещая на одном и том же месте страницы разного содержания. Таким образом, компонент TPageControl является контейнером для размещения нескольких страниц, которые можно открывать в процессе работы приложения спомощью щелчка на соответствующей вкладке.
Чтобы создать новую страницу диалога с соответствующей вкладкой, выберите опцию New Page из контекстного меню данного компонента.
Можно активизировать конкретную страницу одним из следующих способов: с помощью мыши, выбрав ее из выпадающего списка свойства ActivePage, а также перелистывая вкладки с помощью опций Next Page и Previous Page контекстного меню. Свойство PageIndex содержит номер активной страницы. Работу с вкладками реализует встроенный компонент управления TTabSheet.
Компонент TListView отображает поле с иерархическим (древовидным) списком элементов в различных видах. Свойство ViewStyle определяет вид отображения элементов списка: по столбцам с заголовками, вертикально, горизонтально, с малыми или с большими пиктограммами. Свойство Items позволяет добавлять, вычеркивать и модифицировать подписи, а также подбирать пиктограммы для элементов списка. Редактор списка вызывается кнопкой в графе значений этого свойства. Для выбора источника пиктограмм из выпадающего списка свойства LargeImages (SmallImages) задайте значения vsIcon (vsSmallIcon) для свойства ViewStyle. В режиме AutoArrange свойства IconOptions пиктограммы выравниваются в соответствии с выбранным значением свойства Arrangement, а свойство WrapText указывает необходимость переноса текста подписи, когда она не умещается на пиктограмме по ширине.
Компонент TImageList создает контейнер для коллекции из графических изображений одинакового размера K*k, каждое из которых можно выбирать по его индексу в интервале значении от 0 до n-1.
Графические коллекции используются для эффективного обслуживания больших наборов битовых образов или пиктограмм, которые хранятся как единый битовый образ шириной k*n. Окно редактора коллекции изображений открывается двойным щелчком мышью по компоненту или опцией ImageList Editor из его контекстного меню.
Компонент THeaderControl создает контейнер для набора заголовков столбцов, ширину которых можно менять в процессе выполнения программы. Заголовки, перечисленные в свойстве Sections, можно размещать над информационными полями, например, над списками компонентов TListBox. Окно редактора заголовочных секций открывается кнопкой в графе значений этого свойства.
Компонент TRichEdit отображает область редактируемого ввода множества строк информации в формате RTF, который включает различные вариации атрибутов шрифта и форматирования абзацев.
Данный формат принимают многие профессиональные текстовые процессоры, например, Microsoft Word. Компонент TRichEdit является прямым производным классом от класса TCustomRichEdit, полностью наследуя его свойства, методы и события.
Компонент TStatusBar создает строку панели состояния (обычно выравниваемую по нижней границе формы) для отображения статусной информации, выдаваемой при работе программы.
Каждая панель представлена в списке свойства Panels. Панели нумеруются слева направо, начиная с индекса 0. Окно редактора панелей открывается кнопкой в графе значений этого свойства. Свойство SimplePanel используется для переключения вида отображения строки состояния (одно- или многопанельная строка состояния).
Компонент TTrackBar создает шкалу с метками и регулятором текущего положения (вариант линейки прокрутки).
Компонент TProgressBar создает индикатор, который отслеживает процесс выполнения некоторой процедуры в программе. По мере выполнения процедуры, прямоугольный индикатор, например, постепенно окрашивается слева направо заданным цветом.
Компонент TUpDown создает спаренные кнопки со стрелками вверх и вниз. Нажатие этих кнопок вызывает, соответственно, увеличение или уменьшение численного значения свойства Position. Данный компонент обычно используется совместно с компонентом TEdit для ввода целых чисел.
Компонент THotKey используется для установки клавиш быстрого вызова (shortcut) во время выполнения программы. Пользователь может ввести комбинацию "горячих" клавиш, обычно состоящую из модификатора (Ctrl, Alt или Shift) и любого символа, включая функциональные клавиши F1,…,F12. Введенную комбинацию, записанную в свойстве HotKey, можно присвоить свойству Shortcut другого компонента. Чтобы выбрать горячие клавиши на стадии проектирования, используйте свойства HotKey и Modifiers, а чтобы отменить их – свойство InvalidKeys. Чтобы изменить комбинацию во время выполнения программы, удерживайте нажатой клавишу модификатора и одновременно введите новый символ.
Компоненты вкладки Additional Палитры компонентов осуществляют включение в программу следующих элементов управления.
Компонент TBitBtn создает кнопку с изображением битового образа.
КомпонентTSpeedButton создает графическую кнопку, обычно располагаемую на панели (TPanel), для быстрого вызова определенных команд меню или установки режимов. Различным состояниям быстрой кнопки (например, "нажата", "отпущена", "запрещена" ) могут соответствовать разные графические образы. Окно редактора файлов изображений открывается кнопкой в графе значений свойства Glyph.
Компонент TStringGrid создает регулярную сетку для отображения символьных последовательностей по строкам и столбцам.
Во время выполнения программы символьные последовательности и связанные с ними объекты некоторого столбца сетки адресуются свойством Cols. Свойство Rows позволяет подобным образом оперировать со строками сетки. Все символьные последовательности сетки содержатся в свойстве Cells, которое адресует нужную ячейку сетки.
Компонент TDrawGrid создает регулярную сетку для отображения структурированных графических данных по строкам и столбцам.
Свойства RowCount и ColCount задают число ячеек сетки по вертикали и по горизонтали. Значение свойства Options позволяет изменить вид сетки (например, с разделительными линиями между столбцами) и ее поведение (например, с переходом от столбца к столбцу по клавише Tab). Ширина разделительных линий сетки задается свойством GridLineWidth, а линейки прокрутки добавляются свойством ScrollBars.
Свойства FixedCols и FixedRows позволяют запретить прокрутку столбцов и строк, а свойство FixedColor присваивает определенный цвет всем столбцам и строкам.
Во время работы программы вы можете получить в свое распоряжение область для рисования некоторой ячейки с помощью метода CellRect. Метод MouseToCell возвращает координаты номера столбца и строки ячейки, на которую установлен курсор мыши.
Выбранная ячейка сетки становится значением свойства Selection.
Компонент TImage создает на форме контейнер графического изображения (битового образа, пиктограммы или метафайла). Окно редактора файлов изображений открывается кнопкой в графе значений свойства Picture. Чтобы контейнер изменил свои размеры так, чтобы вместить изображение целиком, установите значение true свойства AutoSize. Чтобы исходное изображение меньшего размера растянулось на весь контейнер, задайте значение true свойства Stretch. Используйте методы LoadFromFile и SaveToFile объектного свойства Picture для динамической загрузки и сохранения файлов изображений с помощью инструкций типа:
Image->Picture->LoadFromFile("");
Image->Picture->SaveToFile("");
Компонент TShape рисует простые геометрические фигуры:
окружность и эллипс, квадрат и прямоугольник (можно с закругленными углами).
Компонент TВevel cоздает линии, боксы (контейнеры) или рамки, которые выглядят объемными. Рисуемый компонентом объект определяется свойством Shape, а значение свойства Style меняет вид объекта, делая его выпуклым или вдавленным. Чтобы сохранить относительное положение объекта неизменным, установите значение true свойства Align.
Компонент TScrollBox создает в окне бокс переменного размера, который автоматически снабжается линейками прокрутки.
Компонент TOpenDialog используется для выбора и открытия файлов. Компонент TSaveDialog предназначен для выбора и сохранения файлов. Компонент TOpenPictureDialog используется для выбора и открытия графических файлов. Компонент TFontDialog высвечивает диалоговое окно для выбора шрифтов. Компонент TColorDialog отображает диалоговое окно для выбора цвета. Компоненты становятся активными после вызовы метода Execute(). Например:
if(OpenDialog1->Execute()){ filename = OpenDialog1->FileName;
1. Назовите разновидности меню и укажите их отличия.
2. Опишите технологию создания главного меню.
3. Как скопировать нужные разделы из главного меню во всплывающее меню?
4. Как создать всплывающее меню и закрепить его за компонентом?
5. Как ограничить типы вводимых символов в текстовые компоненты?
6. Чем отличается обработка событий OnKeyPress и OnKeyDown?
7. Поясните действие следующего фрагмента программы:
for (int i=0; iOnClick=0;
Button1->OnClick=Button2Click;
1. Ввести и транспонировать квадратную матрицу.
2. Уплотнить заданную матрицу, удаляя из нее строки и столбцы, заполненные нулями.
3. Разработать текстовый редактор.
4. Создать записную книжку.
3. СТРОКИ И ПОТОКИ ВВОДА/ВЫВОДА В
В C++ Builder можно использовать следующие текстовые строки:символьные массивы типа char* языка С с нуль-символом (‘\0’) в конце;
класс string из стандартной библиотеки C++; класс String (другое название AnsiString) из библиотеки VCL.
Символьные массивы. В языке С строки представляют собой массив данных символьного типа, заканчивающийся символом ‘\0’. Для работы со строками в стандартную библиотеку языка С включен ряд функций, которые определены в заголовочном файле. Например:
strcpy() – копирование одной строки в другую;
strcat() – добавление одной строки в конец другой;
strcmp() – сравнение двух строк в лексикографическом порядке;
strstr() – поиск в строке заданной подстроки;
strupr() – преобразование символов строки к верхнему регистру;
strlen() – определение длины строки без учета нуль-символа.
Использование символьных массивов часто оказывается самым удобным при разработке приложений.
Класс string языка C++. Класс string (название начинается со строчной буквы) представляет собой динамический массив и дает преимущества при выполнении операций соединения и обрезания строк, вычеркивания части строки, поиска комбинаций символов. Данный класс определен в заголовочном файле.
Класс string содержит несколько конструкторов, из которых приведем объявления следующих:
1) string();
2) string(const string &s);
3) string(const char *cp);
4) string(const char *cp, size_t n);
5) string(const string &s, size_type start, size_type length);
6) string(size_type n, char c);
Первый конструктор создает пустую строку. Второй конструктор является конструктором копирования. Третий конструктор создает строку, инициализированную С-строкой (нуль-символ в объект класса string не копируется). Четвертый конструктор создает строку, инициализированную не более чем n первыми символами С-строки. Пятый конструктор создает строку из другой строки, начиная с индекса start и содержащую не более чем length символов. Шестой конструктор создает строку, содержащую n символов c.
Пример.
#include #include using namespace std;
void main(){ // Создание пустой строки и ее вывод string str1;
coutRefresh();
catch(EDBEngineError* dbError) // обработка ошибок BDE for (int i=0; iErrorCount; i++) MessageBox (0, dbError[i].Message.c_str(), "SQL Error", MB_OK) ;
Database1->Rollback() ;
return;
} catch (Exception* exception) // обработка других исключений Остановимся на особенностях использования компонента навигатора TDBNavigator. Нажимая на кнопки компонента First, Prior, Next и Last, можно перемещаться от записи к записи, а с помощью кнопок Insert, Delete, Edit, Post, Cancel и Refresh – производить редактирование.
Свойство DataSource соединяет кнопки управления панели навигатора с компонентами доступа к наборам данных через компонент источника. Изменяя значение этого свойства во время выполнения программы, можно использовать один и тот же компонент для навигации по разным таблицам. Например, можно разместить на форме два компонента редактируемого ввода DBEdit1 и DBEdit2, связанные с таблицами CustomersTable и OrdersTable через источники данных CustomersSource и OrdersSource, соответственно. Когда пользователь выбирает название фирмы (поле Company в DBEdit1), навигатор тоже должен соединяться с источником CustomersSource, а когда активизируется номер заказа (поле OrderNo в DBEdit2), навигатор должен переключаться на источник OrdersSource. Чтобы реализовать подобную схему работы навигатора, необходимо написать обработчик события OnEnter для одного из объектов компонента редактирования, а затем присвоить этот обработчик другому объекту.
Свойство VisibleButtons позволяет убрать ненужные кнопки, например, кнопки редактирования на форме, предназначенной для просмотра данных. Во время выполнения программы можно динамически прятать или вновь показывать кнопки навигатора – в ответ на определенные действия пользователя или на изменения состояния приложения. Предположим, вы предусмотрели единый навигатор для редактирования таблицы CustomersTable и для просмотра таблицы OrdersTable. Когда навигатор подключается ко второй таблице, желательно спрятать кнопки редактирования Insert, Delete, Edit, Post, Cancel и Refresh, а при подключении к первой таблице – снова показать их.
СвойствоShowHint разрешает или запрещает высвечивать подсказку с названием кнопки навигатора, когда на нее наведен курсор. Значение false (устанавливается по умолчанию) запрещает подсказки для всех кнопок.
Свойство Hints содержит массив текстовых подсказок для каждой кнопки навигатора Итак, проектирование формы приложения СУБД в среде C++Builder в простейшем случае требует выполнения следующих действий:
1. Перенесите на форму компонент TTable или TQuery со страницы Data Access и установите его свойства.
2. Перенесите на форму компонент DataSource и в свойстве DataSet укажите ссылку на объект набора данных (например, Table1 или Query1).
3. Перенесите на форму нужные компоненты отображения и редактирования данных со страницы DataControls и в их свойстве DataSource задайте источник данных (например, DataSource1). Определите отображаемое поле набора данных в свойстве DataField.
4. Если на предыдущем шаге вы выбрали компонент TDBGrid, то используйте его совместно с компонентом навигатора TDBNavigator.
1. Что представляет собой псевдоним БД и как он создается?
2. Как создать таблицу с помощью программы Database Desktop?
Какие другие средства для создания таблиц можно использовать?
3. Какие компоненты используются для связи таблиц БД с компонентами визуализации и управления данными DBGrid, DBEdit?
4. Как связать компоненты Table и Query с нужной таблицей БД ?
5. Как связать компонент DBNavigator с нужной таблицей БД ?
6.Что такое первичные и вторичные индексы для таблицы и как их создать?
7. Что такое SQL и из каких частей он состоит?
8. Что означают следующие SQL-запросы:
a) SELECT name, projectname FROM emploee, project WHERE empno=team_leader;
б) SELECT name, salary FROM emploee, prohibit WHERE salary>2900;
9. Как использовать SQL-запрос в C++Builder ?
В следующих упражнениях создать указанные таблицы и записать SQL запрос для выборки данных из обеих таблиц.
1. Построить головную таблицу «Телефонный справочник» с полями:
Номер телефона, Адрес, а также вспомогательную таблицу «Население города» с полями: Номер телефона Фамилия И.О., Год рождения, Пол, Адрес.
2. Головная таблица содержит данные о подразделениях предприятия: Отдел – номер, название(например, Цех1, Бухгалтерия), тип отдела (например, Управление, Производство и т. д.). Вспомогательная таблица содержит сведения о сотрудниках: ID, Фамилия И.О., Год рождения, Пол, Номер_Отдела. Связать таблицы по полю Номер_Отдела.
3. БД содержит следующие связанные таблицы: «Сведения о покупателях»:ID, Фамилия И.О., адрес, Номер банковского счёта, номер заказа и «Сведения о заказах»: Наименование товара, ID, Количество, Стоимость, Дата заказа.
4. БД содержит следующие связанные таблицы: «Студенты»: Номер зачётной книжки, Фамилия, Имя, Отчество, Специализация, Курс, Группа; «Учебный план»: Специализация, Курс, Предмет1, Предмет2,…;
«Журнал успеваемости»: Фамилия, Имя, Отчество, Предмет 1, Предмет 5. БД «Абитуриенты» содержит следующие связанные таблицы:
«Сведения об абитуриентах»: Номер личной карточки, Фамилия, Имя, Отчество, Факультет, Специальность,; «Сведения о сдаваемых предметах»: Специальность, Предмет; «Сведения об оценках»: Фамилия, Имя, Отчество, Номер личной карточки, Предмет, Оценка.
«Общие сведения об автомобиле»:ID, Марка, Тип, Производитель; «Характеристика автомобиля»: Марка, Мощность двигателя, Тип двигателя, Стоимость.
7. БД «Лабораторные занятия» содержит следующие таблицы:
«Преподаватели»: ID, Фамилия, Имя, Отчество, Предмет, Курс, Группа;
«Студенты»:ID, Фамилия, Имя, Отчество, Курс, Группа;
«Пропуски занятий»: Фамилия, Имя, Отчество, Курс, Группа, Предмет, Пропущено.
«Расписание заседаний»:Номер, Название секции, Дата, Время;
«Сведения об участниках»: Фамилия И.О., Название секции, Название доклада; «Оргработа»: Фамилия, Имя, Отчество, Дата приезда, Дата отъезда, Потребность в гостинице, Оргвзнос.
«Общая характеристика единицы хранения»: Инвентарный номер, Тип издания (журнал, книга, рукопись), Название; «Характеристика издания»: Тип издания, Авторы, Название, Издательство, Год издания, Номер, Количество страниц.
10. БД «Магазин» содержит следующие таблицы: «Товары»: Артикул, Наименование товара, Количество, Дата поставки, Цена; «Поставщики»:
Название организации, Наименование товара, Количество, Дата поставки, Адрес организации;
«Покупатели»: Название организации, Наименование товара, Количество, Дата покупки, Адрес покупателя.
«Пациенты»:ID, Фамилия И. О., Диагноз, Возраст, Участок.
12. БД «Спортивная база» содержит следующие таблицы:
«Тренеры»:ID, Фамилия, Имя, Отчество, Вид спорта, Секция;
«Спортсмены»: ID, Фамилия, Имя, Отчество, Вид спорта, Секция, Рейтинг, Возраст.
6. СЕТЕВЫЕ ПРОГРАММЫ И СОКЕТЫ
Понятие «сокет» (socket) означает "гнездо", "разъем" по аналогии с гнездами на аппаратуре. В соответствии с этой аналогией, можно связать два "гнезда" соединением и передавать между ними данные. Каждое гнездо принадлежит определённому хосту (host – хозяин, держатель).Каждый хост имеет уникальный IP (Internet Packet) адрес, представляющий группу из четырех чисел, разделенных точками.
Переданная по IP адресу на хост информация поступает на один из портов хоста. Порт определяется числом от 0 до 65535. После того, как сокет установлен, он имеет вполне определённый адрес, записывающийся так [host]:[port]. Например, 127.0.0.1:8888 означает, что сокет занимает порт 8888 на хосте 127.0.0.1(на данном компьютере). Чтобы не использовать труднозапоминаемый IP адрес, для доступа к хостам используется система имен DNS (DNS – Domain Name Service), поддерживаемая специальным сервером. Цель этой системы – сопоставлять IP адресам символьные имена. Например, адресу "127.0.0.1" в большинстве компьютеров соответствует имя "localhost", что означает сам компьютер, на котором выполняется программа.
Сокеты – это абстракция, представляющая узлы соединения двух приложений - клиента и сервера. К этим узлам подключаются клиенты и серверы через соединение, которое можно представить как гипотетический кабель. Соединения с приложением-сервером. всегда устанавливают приложения-клиенты. В обязанность приложения-сервера входит прослушивание клиентов и ожидание соединения. После этого клиенты посылают серверу или получают от сервера сообщения в виде последовательности символов, а в конце работы закрывают соединение.
Для организации сетевых соединений (сокетов) в C++ Builder используются классы TСlientSocket и TServerSocket из группы компонентов Internet. Ниже описывается работа с компонентом TСlientSocket по установке соединения.
Определение свойств Host и Port. Поместим компонент TClientSocket на форму. Чтобы установить соединение, нужно присвоить свойствам Host и Port компонента TClientSocket значения, соответствующие адресу сервера. Host – это символьное имя компьютера-сервера, с которым надо соединиться (например: localhost, nitro.borland.com или mmf410-2), либо его IP-адрес (например: 127.0.0.1, 192.168.0.88). Port – номер порта (от 1 до 65535) на данном хосте для установления соединения. Обычно номера портов выбираются, начиная с 1001 (номера меньше 1000 могут быть заняты системными службами, например, по умолчанию POP – 110, Http – 80).
Открытие сокета. После назначения свойствам Host и Port соответствующих значений, можно приступить к открытию сокета. Для этого нужно присвоить свойству Active значения true. Здесь полезно вставить обработчик исключительной ситуации на случай, если соединиться не удается. Открытие сокета можно выполнить, также с помощью метода ClientSocket->Open();
Авторизация. На этом этапе вы посылаете серверу свой логин (имя пользователя) и пароль. Этот пункт можно пропустить, если сервер не требует ввода логинов или паролей.
Посылка/прием данных – это, собственно и есть то, для чего открывалось сокетное соединение;
Закрытие сокета – после выполнения операций необходимо закрыть сокет, присвоив свойству Active значение false или вызовом метода ClientSocket->Close();.
Свойства и методы компонента TClientSocket. На рис. 20 и 21 показаны свойства и события компонента TClientSocket в инспекторе объектов. В табл. 18 приведены их краткие описания.
Пример программы-клиента на основе сокета. Поместим на форму компонент TClientSocket, две кнопки Button1 и Button2, два окна типа TEdit и два компонента Memo1 и Memo2. При нажатии на кнопку GetFromServeSendToClient вызывается обработчик события OnClick – Button1Click(). Перед этим в Edit1 нужно ввести хост-имя, а в Edit2 – порт удаленного компьютера. Когда TClientSocket должен прочитать информацию из сокетного соединения, возникает событие OnRead и вызывается функция ClientSocketRead().
Active – True – сокет открыт, а OnConnect – возникает при установлении соFalse–закрыт. единения. В обработчике события можно наHost – строка (типа String), указы- чинать авторизацию или прием/передачу данвающая на имя компьютера, к кото- ных.
рому следует подключиться. OnConnecting – возникает при установлении Address – строка (типа String), ука- соединения, но соединение еще не установлезывающая на IP-адрес компьютера, но. Обычно такие промежуточные события иск которому следует подключиться. пользуются для обновления статуса.
Если вы укажете в Host символьное OnDisconnect – возникает при закрытии сокета имя компьютера, то IP адрес будет из вашей программы либо из-за сбоя.
запрошен у DNS. OnError – возникает при ошибке в работе соPort – номер порта (от 1 до 65535), к кета. Операторы открытия сокета следует закоторому следует подключиться. ключить в блок try..catch.
Service – строка, определяющая OnLookup – возникает при попытке получения службу (ftp, http, pop и т.д.), к порту от DNS IP-адреса хоста.
которой произойдет подключение. OnRead – возникает, когда удален-ный компьClientType – тип соединения ctNon- ютер послал данные.
Blocking или ctBlocking OnWrite – возникает, когда вам разрешена запись данных в сокет.
На рис. 22 показана форма приложения в процессе проектирования.
Ниже приводятся коды приложения-клиента.
TForm1 *Form1;
//--------------------------------------------------------------------------fastcall TForm1::TForm1(TComponent* Owner) //открытие клиента и установка соединения void fastcall TForm1::Button1Click(TObject *Sender) TClientSocket *cs = Form1->ClientSocket; // формируем указатель cs->Close();
// закрытие соединения(если слушали порт ранее) cs->Port=Form1->PortEdit->Text.ToInt();//получение номера порта cs->Host = HostEdit->Text; // получения адреса хоста cs->Active = true; // установка соединения catch(...) Memo1->Text = "Some problem with connection";// если введены //очистка окна void fastcall TForm1::ClearClick(TObject *Sender) Memo1->Clear();
// считывание клиентом сообщения void fastcall TForm1::ClientSocketRead(TObject *Sender, TCustomWinSocket *Socket) Memo1->Text = Socket->ReceiveText(); // запись сообщения в поле клиента void fastcall TForm1::ClientSocketWrite(TObject *Sender, TCustomWinSocket *Socket) Socket->SendText(Memo2->Text); // //вызов события посылки сообщения void fastcall TForm1::Button2Click(TObject *Sender) Form1->ClientSocketWrite(Form1,Form1->ClientSocket->Socket);
Ниже описываются методы свойства Socket компонента TClientSocket. Свойство Socket в свою очередь является объектом класса TСustomWinSocket.
SendText(String) – посылка текстовой строки через сокет.
SendStream() – посылка содержимого указанного потока через сокет.
Пересылаемый поток должен быть открыт.
SendBuf(Buf, Count) – посылка буфера через сокет. Буфером может являться любой тип, например, структура или простой тип int. Буфер указывается параметром Buf, вторым параметром необходимо указать размер пересылаемых данных в байтах (Count).
ReceiveText() – получить сообщение.
Программирование серверов на основе сокетов. Следует заметить, что для сосуществования отдельных приложений клиента и сервера не обязательно иметь несколько компьютеров. Достаточно иметь лишь один, на котором можно одновременно запустить и сервер, и клиент. При этом в качестве имени компьютера, к которому надо подключиться, нужно использовать хост-имя localhost или IP-адрес – 127.0.0.1.
Сервер, основанный на сокетном протоколе, позволяет обслуживать сразу множество клиентов. Причем, ограничение на их количество вы можете указать сами (или убрать это ограничение, как сделано по умолчанию). Для каждого подключенного клиента сервер открывает отдельный сокет, по которому можно обмениваться данными с клиентом. Другим решением является создание для каждого подключения отдельного процесса (Thread).
Создание сервера включает следующие шаги:
Определение свойств Port и ServerType – чтобы к серверу могли подключаться клиенты, порт, используемый сервером, должен совпадать с портом, используемым клиентом. Свойство ServerType определяет тип подключения.
Открытие сокета – открытие сокета и указанного порта. Осуществляется установкой свойства ServerSocket->Active=true. Автоматически начинается ожидание подсоединения клиентов.
Подключение клиента и обмен данными с ним – клиент устанавливает соединение и начинается обмен данными с ним.
Отключение клиента – клиент отключается и закрывается его сокетное соединение с сервером.
Закрытие сервера и сокета – по команде администратора сервер завершает свою работу, закрывая все открытые сокетные каналы и прекращая ожидание подключений клиентов.
Далее приводится краткое описание компонента TServerSocket. На рис. 23 и 24 приводятся свойства и события компонента, соответственно, которые отображаются в окне Инспектора объектов. Табл. 19 содержит описание указанных свойств и событий.
Свойство Socket компонента TServerSocket. Как же сервер может отсылать данные клиенту или принимать данные? Если вы работаете через события OnClientRead и OnClientWrite, то общаться с клиентом можно через свойство Socket (TCustomWinSocket). Отправка/посылка данных через свойство Socket класса TServerSocket аналогична работе клиента( методы SendText(), SendBuf(), SendStream(), ReceiveText()). Однако, следует выделить некоторые полезные свойства и методы, характерные для для сервера: ActiveConnections (Integer) – количество подключенных клиентов; ActiveThreads (Integer) – количество работающих процессов;
Connections (array) – массив, состоящий из отдельных классов TClientWinSocket для каждого подключенного клиента. Например, такая команда ServerSocket1->Socket->Connections[i]->SendText(“Hello!”);
отсылает i-тому подключенному клиенту сообщение “Hello!”);
IdleThreads (Integer) – количество свободных процессов (такие процессы кэшируются сервером, см. свойство ThreadCacheSize); LocalAddress, LocalHost, LocalPort – соответственно, локальный IP-адрес, хост-имя, порт;
RemoteAddress, RemoteHost, RemotePort – соответственно, удаленный IPадрес, хост-имя, порт; методы Lock и UnLock – соответственно, блокировка и разблокировка сокета.
Socket – класс TServerWinSocket, через OnClientConnect – возникает, когда кликоторый вы имеете доступ к открытым ент установил сокетное соединение и ServerType – тип сервера. OnClientDisconnect – возникает, когда ThreadCacheSize – количество клиент- клиент отсоединился.
ских процессов (Thread), которые будут OnClientError – возникает, когда опекэшироваться сервером. Кэширование рация завершилась неудачно.
происходит для того, чтобы не созда- OnClientRead – возникает, когда клиент вать каждый раз отдельный процесс и передал серверу данные.
не уничтожать закрытый сокет, а оста- OnClientWrite – возникает, когда сервер Active –Значение True указывает на то, OnGetSocket – в обработчике этого сочто сервер работает и готов к приему бытия можно отредактировать параметр клиентов, а False – сервер выключен. ClientSocket.
Port – номер порта для установления OnGetThread – в обработчике события соединений. Порт у сервера и у клиен- можно определить уникальный процесс тов должны быть одинаковыми. Реко- (Thread) для каждого клиента канала, мендуются значения от 1025 до 65535. присвоив параметру SocketThread нужService – строка, определяющая службу ную подзадачу TServerClientThread.
(ftp, http, pop и т.д.), порт которой будет OnThreadStart, OnThreadEnd – вознииспользован. Тип: String. кает, когда процесс Thread запускается Пример. Создадим приложение-сервер. На форму нужно поместить кнопки Button1 и Button2, поле Edit1 и компоненты Memo1 и Memo2.
При создании формы вызывается обработчик события OnCreate (FormCreate), в котором активизируется сервер. Свойство компонента TServerSocket->Active устанавливается в true, что означает, что сокетное соединение открыто и доступно для коммуникации с клиентами. В компоненте Edit1 указано значение прослушиваемого порта. Когда ServerSocket должен записать информацию в ClientSocket1, возникает событие OnClientWrite и вызывается функция ServerSocketClientWrite(). На рис. 25 приведена форма приложения в процессе проектирования.
Ниже приводится листинг приложения.
void fastcall TForm1::FormCreate(TObject *Sender) TServerSocket *ServerSocket = Form1->ServerSocket; // указатель на //сервер сокет ServerSocket->Close(); // закрытие соединения(если слушали порт //Ранее) ServerSocket->Port = Form1->Edit1->Text.ToInt(); // получение номера //порта ServerSocket->Active = true; //прослушивание данного порта //открытие сервера по нажатию кнопки void fastcall TForm1::Button2Click(TObject *Sender) ServerSocket->Close(); // закрытие соединения ServerSocket->Port=Form1->Edit1->Text.ToInt(); //получение порта ServerSocket->Active = true; //прослушивание данного порта //--------------------------------------------------------------------------void fastcall TForm1::ServerSocketClientWrite(TObject *Sender, TCustomWinSocket *Socket) //запись сообщения Socket->SendText(Memo1->Text);// отправка сообщения void fastcall TForm1::Button1Click(TObject *Sender) Memo1->Clear();
//--------------------------------------------------------------------------void fastcall TForm1::ServerSocketClientRead(TObject *Sender, TCustomWinSocket *Socket) Memo2->Text = Socket->ReceiveText();
//отправка сообщения i-тому клиенту void fastcall TForm1::Button3Click(TObject *Sender) int n=ServerSocket->Socket->ActiveConnections;
for(int i=0;i< n ;i++) ServerSocket->Socket->Connections[i]->SendText( Memo1->Text+IntToStr(i));
Ниже рассматриваются некоторые приемы работы с компонентом TServerSocket.
Хранение уникальных данных для каждого клиента. Если сервер будет обслуживать множество клиентов, то потребуется хранить информацию для каждого клиента (имя и др.), причем с привязкой этой информации к сокету данного клиента. В некоторых случаях делать все это вручную (привязка к конкретному сокету, массивы клиентов и т.д.) не очень удобно. Поэтому для каждого сокета существует специальное свойство Data для хранения указанной информации.
Посылка файлов через сокет. Рассмотрим посылку файлов через сокет. Достаточно открыть этот файл как файловый поток (TFileStream) и отправить его через сокет (SendStream). Поясним это на примере:
ServerSocket1->Socket->Connections[i]->SendStream(srcfile);
Нужно заметить, что метод SendStream() используется не только сервером, но и клиентом.
Передача блоков информации. Посылаемые через сокет данные могут не только объединяться в один блок, но и разъединяться по нескольким блокам. Дело в том, что через сокет передается обычный поток, но в отличие от файлового потока (TFileStream) он передает данные медленнее (сеть, ограниченный трафик и т.д.). Именно поэтому две команды:
ServerSocket1->Socket->Connections[0]->SendText(“Hello,”);
ServerSocket1->Socket->Connections[0]->SendText(“world!”);
совершенно идентичны одной команде:
ServerSocket1->Socket->Connections[0]->SendText(“Hello, world!”);
Поэтому, если отправить через сокет файл, скажем, в 100 Кб, то получателю блока придет несколько блоков с размерами, которые зависят от трафика и загруженности линии. Причем, размеры не обязательно будут одинаковыми. Отсюда следует, что для того, чтобы принять файл или любые другие данные большого размера, следует принимать блоки данных, а затем объединять их в одно целое (и сохранять, например, в файле). Другим решением данной задачи является тот же файловый поток TFileStream (либо поток в памяти TMemoryStream). Принимать блоки данных из сокета можно через событие OnRead (OnClientRead), используя универсальный метод ReceiveBuf(). Определить размер полученного блока можно методом ReceiveLength().
Приведем еще один простой пример приложений для создания клиента и сервера в одном приложении, соединения между ними и обмена данными. Ниже приводится соответствующий программный код.
//-- Клиент ------------------------------------------------------------------void fastcall TForm1::ClientSocketRead(TObject *Sender, TCustomWinSocket *Socket) Edit1->Text = Socket->ReceiveText();} void fastcall TForm1::Button1Click(TObject *Sender) { ClientSocket->Close();
if((Edit1->Text.Length() > 0)&&(Edit2->Text.Length() > 0)) { ClientSocket->Host = Edit2->Text;
ClientSocket->Port = Edit1->Text.ToInt();
ClientSocket->Open();
//-- Сервер------------------------------------------------------------------void fastcall TForm1::FormCreate(TObject *Sender){ ServerSocket->Close(); // закрытие соединения ServerSocket->Active = true; } //активизация сокета //--------------------------------------------------------------------------void fastcall TForm1::FormDestroy(TObject *Sender){ ServerSocket->Close(); } //--------------------------------------------------------------------------void fastcall TForm1::Button1Click(TObject *Sender){ ServerSocket->Socket->Connections[0]->SendText(Edit1->Text);
Потоки. Для создания распределенных приложений, работающих с несколькими клиентами, можно воспользоваться сокетным потоком. Потоки позволяют синхронизировать работу нескольких клиентов. Приведем пример создания многопотокового сервера и клиентской программы. По каждому запросу клиента сервер создаёт поток и определённое время ожидает ответа клиента. Потоки создаваемые сервером, это потомки класса TserverClientThread. Поэтому мы будем объявлять собственный потоковый класс, наследуя его от TServerClientThread. Ниже приводится код программы с комментариями.
//Unit1.h- классы для сервера #ifndef Unit1H #define Unit1H //--------------------------------------------------------------------------class TForm1 : public TForm published: // IDE-managed Components TServerSocket *ServerSocket1;
void fastcall ServerSocket1GetThread(TObject *Sender, TServerClientWinSocket *ClientSocket, TServerClientThread *&SocketThread);
void fastcall Button1Click(TObject *Sender);
void fastcall Button2Click(TObject *Sender);
fastcall TForm1(TComponent* Owner);
extern PACKAGE TForm1 *Form1;
//--------------------------------------------------------------------------Потоки в сервере наследуются от TServerClientThread.
// Мы будем объявлять потоковый класс следующим образом:
class PACKAGE TMyServerThread :
public Scktcomp::TServerClientThread { /* перед тем как завершить поток, устанавливаем FreeOnTerminate в false, и поток останется в кэше. При установке KeepInCache в false, после завершения выполнения потока, он будет удалён. */ fastcall TMyServerThread(bool CreateSuspended, TServerClientWinSocket* ASocket) : Scktcomp::TServerClientThread(CreateSuspended, ASocket) /* Чтобы включить поток, переопределяем метод ClientExecute(), вызываемый из метода Execute() класса TServerClientThread.*/ void fastcall ClientExecute(void);
//Реализация сервера-----------------------------------------------Server-Unit1.cpp *************** // Requires: TServerSocket, TMemo1, Tmemo2, //Button1-ServerOpen, Button2-Exit #pragma hdrstop #include "Unit1.h" #pragma package(smart_init) //-------------------------------------------fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) const int WAITTIME = 60000;//Будем ждать клиента 60с int k=0;
const int BUFSIZE = 32;//Размер буфера чтения/записи /*Вместо компоненты клиентского сокета поток сервер-клиент должен использовать объект ClientSocket класса TServerClientWinSocket, который создаётся, когда ожидающий сокет сервера выполнит клиентское соединение.*/ //Перегрузка метода ClientExecute() класса TServerClientThread void fastcall TMyServerThread::ClientExecute(void) { // убеждаемся, что соединение активно while (!Terminated && ClientSocket->Connected){ try {//используем TWinSocketStream для чтения/записи через // блокирующий сокет TWinSocketStream *pStream=new TWinSocketStream(ClientSocket, WAITTIME);
memset( buffer, 0, sizeof(buffer) );
if (pStream->WaitForData(WAITTIME)) { // даём клиенту 60с для начала записи if (pStream->Read(buffer, sizeof(buffer)) == 0) ClientSocket->Close();
//если не удаётся прочитать через 60с, закрываем соединение else { //помещение текста клиента в Memo1 cервера Form1->Memo1->Lines->Add(String("(Client) ")+String(buffer) ); strcpy(buffer,Form1Memo2->Text.c_str());//из Memo2 в buffer pStream->Write( buffer, sizeof(buffer));}//Возвращаем буфер клиенту else ClientSocket->Close();//если не передается текст /* Для обработки исключений используется HandleException(). */ void fastcall TForm1::ServerSocket1GetThread(TObject *Sender, TServerClientWinSocket *ClientSocket, TServerClientThread *&SocketThread) { //событие создает поток при открытии нового клиентского сокета //метод возвращает поток через SocketThread параметр // Рекомендуется использовать Thread-bloking сервер SocketThread = new TMyServerThread(false, ClientSocket);
k++;
Memo1->Clear();
Memo1->Text="Открыт клиент:" +IntToStr(k);
void fastcall TForm1::Button1Click(TObject *Sender) ServerSocket1->Close();
ServerSocket1->Open();
void fastcall TForm1::Button2Click(TObject *Sender) Close();
//Создание клиента------------------------------------------------ifndef Unit2H #define Unit2H #include #include #include #include #include //--------------------------------------------------------------------------ClientMain.h ****************** class TForm2 : public TForm { published: // IDE-managed Components TClientSocket *ClientSocket1;
TButton *Button1;
TButton *Button2;
TMemo *Memo1;
TButton *Button3;
TLabel *Label1;
TMemo *Memo2;
TLabel *Label2;
void fastcall Button1Click(TObject *Sender);
void fastcall Button2Click(TObject *Sender);
void fastcall ClientSocket1Read(TObject *Sender, TCustomWinSocket *Socket);
void fastcall ClientSocket1Write(TObject *Sender, TCustomWinSocket *Socket);
void fastcall Button3Click(TObject *Sender);
private: // User declarations AnsiString Server;
public: // User declarations fastcall TForm2(TComponent* Owner);
extern PACKAGE TForm2 *Form2;
//--------------------------------------------------------------------------endif //Реализация клиента-------------------------------------------------Client-Unit2.cpp ***************** // Requires: 2 TButtons, 2 TMemo, TClientSocket //--------------------------------------------------------------------------include #pragma hdrstop #include "Unit2.h" #pragma package(smart_init) #pragma resource "*.dfm" TForm2 *Form2;
//--------------------------------------------------------------------------fastcall TForm2::TForm2(TComponent* Owner) //--------------------------------------------------------------------------void fastcall TForm2::Button1Click(TObject *Sender) { if (ClientSocket1->Active) ClientSocket1->Active = false;