БГУ Пособие - Программирование в C++ Builder
.pdfДля динамического формирования текста командной строки SQL во время выполнения программы удобно использовать стандартную функцию языка С sprintf(). Эта функция замещает параметры форматирования (%s, %d, %n и т.д.) передаваемыми значениями. Например, в результате подстановки значений параметров форматирования
tblName = "Student"; fldName = "Kurs"; fldValue =2;
char sqls[80];
sprintf(sqls, "SELECT * FROM %s WHERE %s = %d", tblName, fldName, fldValue);
символьный массив sqls будет содержать следующую команду:
SELECT * FROM Student WHERE Kurs =2.
Далее созданный запрос выполняется
Query1->Close() ; Query1->SQL->Clear() ; Query1->SOL->Add(sqls); try{
Query1 -> Open(); // выполнить команду SELECT
}
catch(EDBEngineError* dbError) // обработка ошибок BDE { }
}
Метод Open() предназначен для передачи серверу команды Select языка SQL для исполнения. Существуют другие команды SQL, например, команда UPDATE, которая обновляет содержимое некоторой записи, но не возвращает какой бы то ни было результат. Для исполнения сервером таких запросов следует использовать метод ExecSQL() вместо метода Open().
5.8. Соединения с базой данных и транзакции
Компонент TDatabase позволяет создавать локальный псевдоним базы данных, не требуя его наличия в конфигурационном файле BDE. Свойство AliasName содержит псевдоним существующей базы данных, определенный утилитой конфигурации BDE. Указание этого свойства является альтернативой значения свойства DriverName.
Свойство DatabaseName позволяет создать локальный псевдоним базы данных в дополнение к значениям AliasName или DriverName.
Свойство DriverName содержит имя драйвера BDE при создании локального псевдонима по значению DatabaseName. Указание этого свойства является альтернативой значения свойства AliasName.
91
Свойство Params содержит строчный массив параметров одиночного соединения (полный путь, имя сервера и т.д.).
Компонента TDatabase может использоваться для управления транзакциями. Примером транзакции является перевод денежных средств с банковских счетов. Такая транзакция состоит в добавлении суммы к новому счету и вычитании этой суммы из исходящего счета. Если выполнение любой из операций терпит неудачу, вся операция считается незавершенной. SQL-серверы дают возможность "прокручивать назад" команды при возникновении ошибки, не производя изменений в базе данных. Как правило, транзакция содержит несколько команд, поэтому начало транзакции надо отметить методом StartTransaction(). Как только транзакция началась, все ее исполняемые команды находятся во временном состоянии до тех пор, пока один из методов Commit() или Rollback() не отметит конец транзакции. Вызов Commit() фактически модифицирует данные, а вызов Rollback() отменяет всякие изменения.
Ниже приводится листинг, который реализует транзакцию по изменению адреса фирмы на примере связанных таблиц CUSTOMER и ORDERS. Старый адрес, введенный пользователем в область редактирования EditOld, заменяется на новый, введенный в область редактирования EditNew. В этом примере компонентный объект Database1 использовался для одиночного соединения с базой данных, поддерживающего выполнение одиночной транзакции. Этот объект необходимо каким-то образом связать с псевдонимом базы данных: или установкой соответствующих свойств компонента, или определив параметры соединения (такие как тип драйвера, имя сервера, имя пользователя, пароль) во время выполнения программы. Воспользуемя первым способом соединения на стадии проектирования формы приложения, установив нужные значения свойств компонента.
void__fastcall TForm1::Button1Click(TObject *Sender) { char sqls[250]; // массив для хранения команды SQL try{
Database1->StartTransaction (); Query1->SQL->Clear () ;
//Изменить EditOld на EditNew в таблице CUSTOMER sprintf(sqls, "UPDATE CUSTOMER SET Addrl = \"%s\" WHERE (Addrl = \"%s\")", EditNew->Text.c_str(), EditOld->Text.c_str()); Query1->SQL->Add(sqls) ;
Query1->ExecSQL (); Query1->SQL->Clear () ;
//Изменить EditOld на EditNew в таблице ORDERS
sprintf(sqls, "UPDATE ORDERS SET ShipToAddrl = \"%s\" WHERE (ShipToAddrl = \"%s\")", EditNew->Text.c_str(),
92
EditOld->Text.c_str()) ; Query1->SQL->Add(sqls) ; Query1->ExecSQL();
// Внести все изменения, сделанные до этого момента
Database1->Commit(); Table1->Refresh(); Table2->Refresh();
}
catch(EDBEngineError* dbError) // обработка ошибок BDE
{
for (int i=0; i<dbError->ErrorCount; i++)
MessageBox (0, dbError[i].Message.c_str(), "SQL Error", MB_OK) ; Database1->Rollback() ;
return;
} catch (Exception* exception) // обработка других исключений {}
}
5.9. Управление данными
Остановимся на особенностях использования компонента навигатора
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 позволяет убрать ненужные кнопки, например, кнопки редактирования на форме, предназначенной для просмотра данных. Во время выполнения программы можно динамически прятать или вновь показывать кнопки навигатора – в ответ на определенные дей-
93
ствия пользователя или на изменения состояния приложения. Предположим, вы предусмотрели единый навигатор для редактирования таблицы 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;
94
б) 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, Предмет
2, …
5.БД «Абитуриенты» содержит следующие связанные таблицы: «Сведения об абитуриентах»: Номер личной карточки, Фамилия, Имя, Отчество, Факультет, Специальность,; «Сведения о сдаваемых предметах»: Специальность, Предмет; «Сведения об оценках»: Фамилия, Имя, Отчество, Номер личной карточки, Предмет, Оценка.
6. БД «Автомобили» содержит следующие таблицы: «Общие сведения об автомобиле»:ID, Марка, Тип, Производитель; «Характеристика автомобиля»: Марка, Мощность двигателя, Тип двигателя, Стоимость.
7. БД «Лабораторные занятия» содержит следующие таблицы: «Преподаватели»: ID, Фамилия, Имя, Отчество, Предмет, Курс, Группа; «Студенты»:ID, Фамилия, Имя, Отчество, Курс, Группа; «Пропуски занятий»: Фамилия, Имя, Отчество, Курс, Группа, Предмет, Пропущено.
95
8. БД |
«Конференция» |
содержит |
следующие |
таблицы: |
«Расписание |
заседаний»:Номер, |
Название |
секции, Дата, |
Время; |
«Сведения об участниках»: Фамилия И.О., Название секции, Название доклада; «Оргработа»: Фамилия, Имя, Отчество, Дата приезда, Дата отъезда, Потребность в гостинице, Оргвзнос.
9. БД «Библиотека» содержит следующие таблицы: «Общая характеристика единицы хранения»: Инвентарный номер, Тип издания (журнал, книга, рукопись), Название; «Характеристика издания»: Тип издания, Авторы, Название, Издательство, Год издания, Номер, Количество страниц.
10. БД «Магазин» содержит следующие таблицы: «Товары»: Артикул, Наименование товара, Количество, Дата поставки, Цена; «Поставщики»: Название организации, Наименование товара, Количество, Дата поставки, Адрес организации; «Покупатели»: Название организации, Наименование товара, Количество, Дата покупки, Адрес покупателя.
11.БД |
«Поликлиника» |
содержит |
|
следующие |
таблицы: |
||
«Врачи»: |
Участок, |
Фамилия |
И.О., |
Специальность; |
|||
«Пациенты»: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 адресам сим-
96
вольные имена. Например, адресу "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 или вызовом метода Cli- entSocket->Close();.
97
Свойства и методы компонента TClientSocket. На рис. 20 и 21 по-
казаны свойства и события компонента TClientSocket в инспекторе объектов. В табл. 18 приведены их краткие описания.
Рис. 20. Свойства компонента TClientSocket
Рис. 21. События компонента TclientSocket
Пример программы-клиента на основе сокета. Поместим на фор-
му компонент TClientSocket, две кнопки Button1 и Button2, два окна типа TEdit и два компонента Memo1 и Memo2. При нажатии на кнопку GetFromServeSendToClient вызывается обработчик события OnClick – Button1Click(). Перед этим в Edit1 нужно ввести хост-имя, а в Edit2 – порт удаленного компьютера. Когда TClientSocket должен прочитать информацию из сокетного соединения, возникает событие OnRead и вызы-
вается функция ClientSocketRead().
98
|
Таблица 18 |
|
Свойства |
События |
|
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 показана форма приложения в процессе проектирования.
Рис. 22. Форма приложения-клиента
Ниже приводятся коды приложения-клиента.
TForm1 *Form1; //---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
}
//открытие клиента и установка соединения
void __fastcall TForm1::Button1Click(TObject *Sender)
99
{
TClientSocket *cs = Form1->ClientSocket; // формируем указатель cs->Close(); // закрытие соединения(если слушали порт ранее)
try
{
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. Буфер
100
