Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Основная экранная форма




Имеются несколько основных компонент (классов), которые используются для доступа к БД. Эти классы делятся на три группы:

  • Невизуальные: TDataBase, TTable, TQuery, TStoredProc
  • Визуальные: TDBGrid, TDBEdit, TDBNavigator, TDBMemo, TDBListBox, TDBComboBox, TDBRichEdit, TDBCtrGrid, TDBChart
  • Связующие: TDataSource

Первая группа включает невизуальные классы, которые используются для управления базами данных, таблицами и запросами. Эта группа сосредотачивается вокруг компонент типа TDataBase, TTable, TQuery. В Палитре Компонент они расположены на странице BDE.

Вторая важная группа классов - визуальные, которые показывают данные пользователю, и позволяют ему просматривать и модифицировать их. Эта группа классов включает компоненты типа TDBGrid, TDBEdit, TDBImage и TDBComboBox. В Палитре Компонент они расположены на странице Data Controls.

Третий тип, используется для того, чтобы связать перечисленные выше два типа объектов. К этому типу относится невизуальный компонент TDataSource (на странице Data Access).

Рассмотрим последовательность действий построения клиентской части программы..\PRG0\SKLAD.DPR.

1. Запустить Delphi(С++Builder6) В результате загрузится новый проект с формой.

2. Выбрать набор инструментов BDE. Перенести на форму копию объекта типа TDataBase.

Компонент TdataBase служит для соединения с базой данных. Настраивая свойства объекта, созданного на базе этого компонента, можно выбирать параметры соединения. В частности свойство Alias Name указывает на псевдоним базы данных. Этот псевдоним выбирается из открывающегося списка, в котором представлены алиасы баз данных, зарегистрированные в программе BDE administrator. Свойство DataBaseName определяет локальный псевдоним БД, который может использоваться при доступе к БД вместо псевдонима BDE. Локальный псевдоним рекомендуется выбирать отличным от внешнего, определенного свойством Alias Name. В противном случае система, при любом обращении к базе данных будет запрашивать у пользователя пароль доступа к БД[9]. Псевдоним, описанный в DataBaseName используется при создании объектов на базе компонент TTable, TQuery, TStoredProc. Свойство Params определяет параметры соединения. В этом свойстве можно можно переустановить параметры псевдонима БД и драйвера, заданные в администраторе базы данных. Установка флажка Exclusive в значение true определяет монопольный режим работы с базой данных.

При двойном щелчке мышью по созданному объекту Database1 появляется окно редактора параметров:

Параметр Login prompt должен иметь значение false. В противном случае, при любом обращении к базе данных система будет запрашиваться пароль доступа к БД. Поле отметки Keep Inactive Connection в отмеченном состоянии означает, что соединение с БД будет сохраняться даже, если ни один набор данных этой базы данных не будет открыт.

Объект типа TDataBase создается для каждого факта соединения с отдельной базой данных. Случаи явного использования в приложении компонента TDataBase чаще всего связаны с работой в архитектуре клиент-сервер. Явно определенный в программе компонент TDataBase облегчает управление БД, создавая постоянные соединения с ней, настраивая сеанс соединения с базой данных, управляя транзакциями, создавая локальные псевдонимы BDE. Каждый набор данных, работающий с таблицами одной и той же БД, может иметь с этой базой отдельное соединение. Однако это нерационально, поскольку каждое соединение требует дополнительных системных ресурсов. Для минимизации их использования рекомендуется создавать единственное соединение с базой данных при помощи компонента TDataBase, а все наборы данных, реализующие действия над БД, соединять с этим компонентом. Соединение активизируется установкой свойства Connected в значение true.

3. Для операций с данными, содержащимися в таблицах, используются наборы данных. Набор данных представляет собой совокупность записей, взятых из одной или нескольких таблиц БД. Записи, входящие в набор данных отбираются по определенным правилам, в частности набор данных может состоять из записей одной таблицы. Доступ к конкретному набору данных регулируется компонентами TTable[10] и TQuery из набора Data Access. При использовании TTable для организации доступа к таблице «Товары» необходимо переместить копию компонента на экранную форму и настроить в инспекторе объектов свойства DataBaseName (в нашем случае, установить алиас UchSklad) и TableName (выбрать из выпадающего списка имя используемой таблицы TOVARY). Кроме того, можно поменять значение Name объекта на TovaryTable.

Заметим также, что установка флажка Exclusive в значение True в случае объекта TTable обеспечивает монопольный режим доступа к таблице, что бывает необходимо при выполнении ряда определенных действий на таблице (создание индексов, изменения структуры, удаление всех записей и пр.).

Доступ к таблице регулируется установкой значения свойства Active в true. В программном коде открытие (закрытие) таблицы осуществляется также с помощью методов TovaryTable.Open (TovaryTable.Close).

Для компонента TQuery[11] вместо свойства TableName заполняется свойство SQL, в котором задается запрос к БД на языке SQL. В частности, запрос на данные таблицы TOVARY имеет вид: SELECT * FROM Tovary. Набор данных TQuery в отличие от компонента TTable может включать записи из более чем одной таблицы.

TTable и TQuery представляют собой пример невизуальных компонент и становятся невидимыми при запуске программы. Для визуализации данных таблицы и интерактивной работы с ними служит компонент TDBGrid в группе Data Controls. Компонент TDBGrid отображает содержимое таблицы в виде сетки, в которой строки соответствуют записям, а столбцы полям таблицы. Используя этот компонент, пользователь может просматривать и редактировать данные. Связь между визуальными и невизуальными компонентами осуществляется компонентом TdataSource. Этот класс используется в качестве проводника между TTable или TQuery и компонентами, визуализирующими данные, типа TDBGrid, TDBEdit и TDBComboBox (data-aware components). В большинстве случаев, все, что нужно сделать с DataSource - это указать в свойстве DataSet соответствующий TTable или TQuery. Затем, у data-aware компонента в свойстве DataSource указывается TDataSource, который используется в настоящее время. Любая таблица может быть открыта на этапе проектирования, присвоением свойству Active значения true. При этом визуальные компоненты, связанные с таблицей, отображают текущее состояние записей соответствующего набора данных, но перемещение и редактирование записей невозможны. Исключение составляет возможность перемещения текущего указателя с помощью полосы прокрутки для визуальной компоненты TDBGrid.

TDataSource имеет свойство Enabled, и оно может быть полезно всякий раз, когда Вы хотите временно отсоединить, например, DBGrid от таблицы или запроса. Это требуется, например, если нужно программно пройти через все записи в таблице. Ведь, если таблица связана с визуальными компонентами (DBGrid, DBEdit и т.п.), то каждый раз, когда Вы вызываете метод TTable.Next, визуальные компоненты будут перерисовываться. Даже если само сканирование в таблице двух или трех тысяч записей не займет много времени, то может потребоваться значительно больше времени, чтобы столько же раз перерисовать визуальные компоненты. В случаях подобных этому, лучше всего установить поле DataSource.Enabled в False. Это позволит просканировать записи без перерисовки визуальных компонент. Такая операция может увеличить скорость в некоторых случаях в десятки и сотни раз.

Свойство TDataSource.AutoEdit указывает, переходит ли DataSet автоматически в режим редактирования при вводе текста в data-aware объекте.

Перенесем компонент TDataSource на форму и установим свойство DataSet равным TovaryTable.

4.Перенесем компонент TDBGrid на форму. Установим значение свойства DataSource равным DataSource1. Добавим на форму компонент TDBNavigator, свойство DataSource которого также установим равным DataSource1. Прежде чем запустить программу добавим на форму компонент TStoredProc, свойство DatabaseName которого установим равным UchSklad, а имя процедуры StoredProcName GET_KOD_TOVAR(Напомним, что эту процедуру мы создали ранее на сервере). Компонент TStoredProcпредназначен для вызова исполняемой процедуры, хранимой на сервере. Для вызова процедуры необходимо заполнить свойства DatabaseName и StoredProcName, а также, если потребуется, задать значения входных параметров, используя инспектор объектов. Для доступа к параметрам при выполнении программы можно использовать метод ParamByName.

Далее найдем для объекта TovaryTable событие AfterInsert и определим обработчик этого события в виде процедуры

Object_Pascal Borland_C++[12]
procedure Form1.TovaryTableAfterInsert (DataSet: TDataSet); begin StoredProc1.ExecProc; TovaryTable.FieldByName('Kod_Tovar').Value:= StoredProc1.ParamByName('NR').Value; end; Void __fastcall TForm1::TovaryTableAfterInsert(TDataSet *DataSet) { StoredProc1->ExecProc(); TovaryTable-> FieldByName("Kod_Tovar")-> Value=StoredProc1-> ParamByName("NR")->Value; }

Оператор StoredProc1. ExecProc (В C++ функция StoredProc1-> ExecProc();)запускает процедуру с именем GET_KOD_TOVAR на сервере. Поскольку наша процедура не имеет входных параметров, настройка их значений не производится. Полученный результат присваивается полю Kod_Tovar[13]. При помощи этой процедуры можно автоматически увеличивать значение поля Kod_Tovar на 1.

Кнопки объекта DBNavigator1 имеют слева направо следующие значения:

Первая запись

Предыдущая

Следующая

Последняя запись

Вставка записи

Удаление записи

Редактирование записи

Подтверждение редактирования

Отказ от изменений

Обновление данных

Изменив соответствующим образом список Hints у навигатора, и установив флажок на форме ShowHint в true, можно получить эти записи в качестве подсказок на экране при фокусировке мыши на соответствующей кнопке.

Аналогичным образом можно получить доступ к записям таблиц «Покупатели» и «Расход товара».

При работе с форматами данных различных регионов бывает необходимо настроить форматы дат, времени, различные разделители. Настройку можно сделать:

¨ на уровне операционной системы (через настройку свойств «языки и стандарты» панели управления)

¨ на уровне BDE (через настройку параметров драйверов в программе BDE administrator)

¨ на уровне клиентской части программы

Ниже приводится пример такой настройки на уровне клиентской части программы. Для этого необходимо построить обработчик события OnCreate формы в виде процедуры

Object_Pascal Borland_C++
procedure TForm1.FormCreate(Sender: TObject); begin DateSeparator:='.'; ShortDateFormat:='dd.mm.yyyy'; ShortTimeFormat:='hh:mm:ss'; end; void __fastcall TForm1::FormCreate(TObject *Sender) { DateSeparator='.'; ShortDateFormat="dd.mm.yyyy"; ShortTimeFormat="hh:mm:ss"; }

Системная переменная (модуля SysUtils) DateSeparator описывает разделитель, переменные ShortDateFormat и ShortTimeFormat задают форматы дат и времени. Процедура отрабатывает при запуске программы при создании формы.

В качестве примера динамического перепрограммирования свойств объектов приведем пример обслуживания одним навигатором двух таблиц «Товары» и «Покупатели». Как уже упоминалось выше, в навигаторе свойство DataSource необходимо настроить на имя компонента TDataSource, связывающий навигатор с определенной таблицей. Изменяя динамически эту связь можно одним навигатором обслуживать различные таблицы.

Перенесем на форму копию компонента Tlabel и напишем обработчик события OnEnter для объекта DBGrid 1.


 

Object_Pascal Borland_C++
procedure TForm1.DBGrid1Enter(Sender: TObject); begin DBNavigator1.DataSource:= DataSource1; DBNavigator1.Enabled:=True; Label1.Caption:='Товары'; end; void __fastcall TForm1::DBGrid1Enter(TObject *Sender) { DBNavigator1-> DataSource=DataSource1; DBNavigator1->Enabled=True; Label1->Caption="Товары"; }

 

Аналогичную процедуру

Object_Pascal Borland_C++
procedure TForm1.DBGrid2Enter(Sender: TObject); begin DBNavigator1.DataSource:= DataSource2; DBNavigator1.Enabled:=True; Label1.Caption:='Покупатели'; end; void __fastcall TForm1::DBGrid2Enter(TObject *Sender) { DBNavigator1-> DataSource=DataSource2; DBNavigator1->Enabled=True; Label1->Caption="Покупатели"; }

будем использовать как обработчик события OnEnter для объекта DBGrid2.

Запустив программу можно убедиться, что любое переключение между объектами Grid для таблиц «Товары» и «Покупатели» изменяет состояние метки Label1, которое показывает, какой таблицей в настоящий момент управляет навигатор.

Для таблицы «Расход товара» построим собственный навигатор, используя методы объекта TTable.

В частности, если на форме имеется объект Table1, то методы Table1.First, Table1.Last, Table1.Next, Table1.Prior [14] – осуществляют соответственно переход к первой, последней, следующей и предыдущей записи.

Добавление новой записи осуществляется с использованием метода Table1.Add или Table1.Insert. Отличие метода Table1.Add от Table1.Insert состоит в том, что в первом случае запись добавляется в конец таблицы, а во втором – запись вставляется непосредственно перед записью, на который указывает курсор. Метод Table1.Delete удаляет текущую запись.

Метод Table1.Edit переводит таблицу в режим редактирования. Методы Table1.Post и Table1.Cancel означают соответственно подтверждение или отказ от сделанных изменений. Метод Table1.Refresh обновляет содержимое таблицы на форме. Необходимо помнить, что всякий раз, когда происходит смещение указателя с текущей записи, в таблице автоматически будут сохранены введенные данные. Это означает, что вызовы First, Next, Prior и Last всегда выполняют Post, если Вы находились в режиме редактирования. Если Вы работаете с данными на сервере и транзакциями, тогда правила, приведенные здесь, не применяются. Тем не менее, даже если Вы не работаете с транзакциями, Вы можете все же отменить результаты вашего редактирования в любое время, до тех пор, пока не вызвали напрямую или косвенно метод Post. Например, если Вы перевели таблицу в режим редактирования, и изменили данные в одном или более полей текущей записи, Вы можете всегда вернуть текущую запись в исходное состояние вызовом метода Cancel.

Наборы данных могут находиться в определенных состояниях. Состояние набора определяется свойством State. Это свойство доступно только для чтения и используется при выполнении операций над записями таблицы (некоторые операции над набором разрешены только в определенных состояниях). Для перевода набора данных в требуемое состояние используются специальные методы.

Перечислим основные состояния набора данных:

dsInactive – набор данных закрыт;

dsBrowse – набор данных находится в состоянии просмотра. Переход в этот режим происходит из режимов dsEdit и dsInsert при вызове одного из методов Post или Cancel.

dsEdit – набор данных находится в состоянии редактирования. Переход в этот режим происходит из режимов dsBrowse при вызове метода Edit.

dsInsert – набор данных находится в состоянии добавления новой записи. Переход в этот режим происходит из режимов dsBrowse при вызове одного из методов Insert, InsertRecord, Append или AppendRecord.

dsSetKey – режим поиска записей, удовлетворяющих некоторому условию. Переход в этот режим происходит из режимов dsBrowse при вызове одного из методов SetKey, FindKey, GotoNearest или FindNearest. Возможен только для компоненты типа TTable, для компоненты TQuery отбор записей осуществляется средствами языка SQL.

dsOpening – набор данных находится в открытом состоянии.

 

 

Построим на форме кнопки «Следующая»(Next) и «Вставить»(Add), выбирая компонент Button из палитры Standart. В обработчик события OnClick первой кнопки вставим процедуру[15]

а в соответствующий обработчик второй кнопки процедуру

Object_Pascal Borland_C++
procedure TForm1.AddClick(Sender: TObject); begin RasxodTable.Insert; end; void __fastcall TForm1::AddClick(TObject *Sender) { RasxodTable->Insert(); }

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

Выше уже отмечалось, что таблицы «Товары» и «Расход товара» связаны по формулам:

¨ Стоимость купленного товара:=Количество купленного товара * цена единицы товара,

¨ Количество товара на складе:= Количество товара на складе – Количество купленного товара.

При этом заметим, «Количество купленного товара» и «цена единицы товара» ( «Количество товара на складе») лежат в разных таблицах. Решением второй задачи мы уже занимались.

Для первого случая поступим следующим образом:

§ Во первых, свяжем эти таблицы операционной связью (Relation, Link, Master-Detal) так, чтобы таблица «Товары» всегда была позиционирована на том товаре, который продается согласно текущей строке таблицы «Расход товара». Это позволит «всегда иметь под рукой» цену продаваемого товара.

Для этого установим у объекта TovaryTable (типа TTable) значение свойства MasterSource равным DataSource3 (который управляет таблицей Rasxod через объект RasxodTable) и значение свойства MasterFields (после щелчка на кнопку «...» появится окно редактора связей, в котором надо выбрать ключи связи KOD_TOVAR->KOD_TOVAR). Объявленная связь сразу проявится – DBGrid1 будет показывать только строку таблицы «Товары», соответствующую текущей строке в «Расход товара»[16].

Аналогично можно связать ведомую (Detal-)таблицу «Покупатели» с ведущей (Master-)таблицей «Расход товара».

§ Теперь для события OnDataChange объекта DataSource3 напишем обработчик[17]

§

Object_Pascal Borland_C++
procedure TForm1.DataSource3DataChange (Sender: TObject; Field: TField); var ss:INTEGER; begin ss:= RasxodTable.FieldByName('Kolvo').Value* TovaryTable.FieldByName('Zena').Value; if (RasxodTable.FieldByName('Stoim').Value<>ss)AND ((RasxodTable.State=dsEdit)OR (RasxodTable.State=dsInsert)) then RasxodTable.FieldByName('Stoim').Value:=ss; end; void __fastcall TForm1::DataSource3DataChange(TObject *Sender, TField *Field) { int ss; ss=RasxodTable-> FieldByName("Kolvo")-> AsInteger * TovaryTable1-> FieldByName("Zena")-> AsInteger; if ((RasxodTable-> FieldByName("Stoim")-> Value!=ss)&& ((RasxodTable->State== dsEdit) ||(RasxodTable-> State==dsInsert))) RasxodTable-> FieldByName("Stoim")-> Value=ss; }

При этом естественно значение свойства ReadOnly столбца Stoim объекта DBGrid3 положить равным True, чтобы запретить независимые корректировки этого реквизита. С этой целью необходимо дважды щелкнуть мышью на объекте DBGrid3, вызвать редактор столбцов и манипулируя левой кнопкой мыши получить доступ ко всем столбцам, в появившемся списке выбрать столбец, характеризующий стоимость и в свойстве ReadOnly выбрать значение True.

§ Остается еще один случай, в котором не обеспечено корректное обновление значение поля «стоимость» - при изменении цены товара в таблице «Товары». Для этого добавим в БД триггер:

CREATE TRIGGER AU_TOVARYI FOR TOVARY AFTER UPDATE POSITION 2

AS BEGIN

IF (OLD.ZENA <> NEW.ZENA) THEN

UPDATE RASXOD SET STOIM=NEW.ZENA*KOLVO

WHERE KOD_TOVAR = OLD.KOD_TOVAR; END

Появление значения OLD.KOD_TOVAR (а не NEW.KOD_TOVAR) в строке условия отбора оператора UPDATE вызвано необходимостью корректно обрабатывать ситуацию, когда одновременно меняются код товара и его цена. Это означает, что системное каскадное обновление выполняется после выполнения триггера AU_TOVARYI, а не до (как казалось...).

Теперь установим контроль условия KOLVO>=0 – обработчик события OnValidate поля Kolvo объекта RasxodTable:

 

Object_Pascal Borland_C++
procedure TForm1.RasxodTableKOLVOValidate(Sender: TField); begin if RasxodTable.FieldByName('Kolvo'). AsInteger<0 then raise Exception.Create('"Количество" должно быть неотрицательным') end; Здесь raise - оператор языка Object Pascal порождающий исключительную ситуацию (принудительное событие «ошибка»), Exception – класс исключительных ситуаций (определенный в модуле SysUtils), Create – конструктор объектов этого класса. void __fastcall TForm1::RasxodTableKOLVOValidate(TField *Sender) { if(RasxodTable-> FieldByName("Kolvo")->AsInteger<0) throw Exception("<Количество> должно быть неотрицательным "); }

 

Таким способом порожденная в OnValidate исключительная ситуация не просто выдает сообщение, но и блокирует подтверждение введенного значения поля.

Прежде чем двигаться дальше, полезно посмотреть поведение программы в действии:

§ Как отрабатывает каскадное обновление таблицы «Расход товара» при удалении строки и изменении кода товара в таблице «Товары». Аналогично, для таких же операций с таблицей «Покупатели», ведь реализация каскадного обновления в этих двух случаях разная.

§ Как отрабатывает контроль ограничений целостности в таблице «Расход товара» при изменении кодов товара или покупателя на отсутствующие в родительских таблицах.

§ Как отрабатывает контроль условия ZENA>=0 и условия KOLVO>=0.

Обратите внимание: реакция на нарушение первого условия появляется только после попытки покинуть текущую строку таблицы, а для второго условия реакция почти мгновенная – программа не позволяет покинуть поле.

Это связано с тем, что второе условие контролирует Delphi-программа, причем именно в момент выхода из поля[18]. А первое условие контролирует InterBase SQL-сервер, причем только после попытки покинуть текущую строку таблицы – только в этот момент Delphi-программа подтверждает изменения (вызывая метод Post) и посылает запрос InterBase SQL-серверу на проведение соответствующих изменений в реальной таблице.

§ Как отрабатывает перерасчет «стоимости» при изменении вместе и по отдельности «количества» и «кода товара» в таблице «Расход товара», а также изменении цены товара в таблице «Товары».

§ Как отрабатывает перерасчет «количества на складе» при таких же изменениях в таблице «Расход товара». И как не выполняется этот перерасчет при изменении «кода товара» в таблице «Товары» и каскадном его изменении в таблице «Расход товара».

§ Как отрабатывает перерасчет «количества на складе» при удалении и добавлении строк в таблице «Расход товара». И как при добавлении отрабатывает генератор «кода покупки».

Теперь внесем в нашу программу еще несколько изменений...\PRG1\SKLAD.DPR

Объявленная нами связь «Detal-ведомая -> Master-ведущая» для таблиц TOVARY->RASXOD ограничила область видимости в Grid таблицы «Товары»[19]. Пока мы работаем в Grid таблицы «Расход товара» это возможно даже и очень удобно. Но когда мы переключаемся на Grid таблицы «Товары» хотелось бы видеть весь список товаров. Для устранения этих проблем:

§ Поместим на экранную форму еще один объект типа TTable, управляющий таблицей TOVARY, и объект типа TDataSource, управляющий ее связью с объектом DBGrid1.

Это легче сделать копированием – щелчок на копируемом объекте, CTRL+C, CTRL+V и мышкой переместить выделенную копию «в сторонку» (осторожно! копия получается точно наложенной на оригинал, но при её перемещении оригинал останется на месте).

§ Установим имя Name TovaryTable1 у нового объекта типа TTable, а у нового объекта типа TDataSource оставим имя DataSource4, предложенное системой, и на всякий случай проверим, что DataSource4 действительно ссылается (в смысле DataSet =) на TovaryTable1. Теперь разграничим функции объектов TovaryTable и TovaryTable1: пусть за связь Master-Detal с таблицей «Расход товара» отвечает TovaryTable1, а за остальное – TovaryTable.

§ У объекта TovaryTable: удалим значения свойств MasterSource, MasterFields и IndexFieldName.

§ У объекта TovaryTable1: удалим вызов обработчиков событий AfterInsert и AfterPost (но не сами процедуры обработки, т.к. они вызываются в объекте TovaryTable).

§ У объекта DataSource3 (связь с таблицей «Расход товара») внесем изменение в обработчик события OnDataChange: в процедуре DataSource3DataChange заменим

TovaryTable.FieldByName('Zena') на TovaryTable1.FieldByName('Zena').

§ Теперь обеспечим переключение так, чтобы таблицей «Товары» управлял объект TovaryTable, когда курсор находится в DBGrid1 таблицы «Товары», и объект TovaryTable1, когда курсор находится в DBGrid3 таблицы «Расход товара».

Для этого внесем изменения в текст обработчика события OnEnter объекта DBGrid1: в процедуру DBGrid1Enter[20] (в начало) добавим

Object_Pascal Borland_C++
DBGrid1.DataSource:=DataSource1; IF DataBase1.Connected then TovaryTable.Refresh; DBGrid1->DataSource= DataSource1; if (Database1->Connected) TovaryTable->Refresh();

 

Далее поправим обработчик события OnEnter объекта DBGrid3: в процедуру DBGrid3Enter (в начало) добавим

Object_Pascal Borland_C++
DBGrid1.DataSource:=DataSource4; TovaryTable1.Refresh; DBGrid1->DataSource= DataSource4; TovaryTable1->Refresh();

 

Аналогичные преобразования надо провести для таблицы «Покупатели» и её связи с таблицей «Расход товара». Теперь мы будем видеть полный список товаров и покупателей в своем «окне просмотра», а находясь в окне просмотра «Расход товаров» будем видеть информацию только о текущем товаре и покупателе. Причем сохранится правильность расчета стоимости продажи, которая существенным образом основана на связи Master-Detal.

Мы убедились, что при добавлении записей генераторы правильно формируют коды соответствующих элементов таблиц. Можно заметить, что значение полей, описывающих эти коды и необходимых для обеспечения уникальности записей, не носят содержательный характер, и могут быть исключены из состава столбов визуальной компоненты TDBGrid. Также желательно наименования столбцов таблиц выводить на русском языке. Для исключения столбца из состава столбцов визуальной компоненты необходимо выбрать мышью объект DBGrid1, в инспекторе объектов найти свойство Columns, открыть окно редактирования столбцов, через контекстное меню создать список, включающий все поля таблицы (далее можно удалить ненужные столбцы из списка). Каждый столбец, являясь объектом, имеет ряд свойств. Для изменения наименования столбца необходимо исправить для каждого поля свойство Caption, входящее в список подсвойств свойства Title.

Записи в Таблице «Расход товара» не содержат наименований товаров и покупателей, а содержат только коды этих реквизитов. Поскольку код товара не несет содержательной информации, а конечный пользователь привык работать с наименованием товара, желательно при показе таблицы «Расход товара» выводить на экран не коды товара и покупателя, а их наименования. Этого можно достичь, используя поля выбора данных(LookUp-поля). Создадим LookUp-поле для наименования товара. [21] Выберем мышью иконку компоненты Ttable для таблицы «Расход товара» и двойным щелчком вызовем пустой список полей таблицы. Вызвав правой кнопкой мыши контекстное меню, выберем режим добавления всех полей, а также режим добавления нового поля. Появившуюся форму необходимо заполнить, как показано ниже

На данном экране описана LookUp-связь по ключевому полю KOD_TOVAR с таблицей «Товары». В результате всех этих действий в таблице «Расход товара» образуется новое текстовое поле TOVAR, которое связано по полю KOD_TOVAR с соответствующим полем таблицы «Товары». Совершенно аналогично поступим с полем POKUP. В столбцах компоненты DBGrid3 появляются элементы ComboBox (с наименованием товара и покупателя) через которые можно осуществить выбор значения соответствующего реквизита.

 

Набор значений полей может быть организован также через компоненты DBText, DBEdit, DBMemo, DBListBox, DBComboBox, DBCheckBox, DBRadioGroup, DBLookUpComboBox и пр. В каждом из этих элементов необходимо настроить свойства DataSource и DataField. [22]


[1] Ключевые поля в таблицах выделены подчеркиванием.

[2] Резервное копирование, вообще говоря, является функцией администратора базы данных, поэтому выполнение этой функции может потребовать соответствующих прав доступа.

[3]) Параметр CHARACTER SET WIN1251 можно опустить, он будет устанавливаться по умолчанию для символьных полей, т.к. ссылка на эту кодировку символьных данных была задана при создании базы данных. Опускать значение параметра COLLATE не рекомендуется, по умолчанию оно может установиться совсем не так, как ожидалось.

 

[4] При необходимости свойства поля таблицы, описанные с использованием типа KEY_TYPE, могут быть изменены при создании таблицы. Для этого после описания типа необходимо описать необходимые значения параметров.

[5] Такая ситуация называется нарушением условия целостности базы данных.

[6] Вообще говоря, можно было при проектировании базы данных не включать поле «Стоимость купленного товара» в таблицу RASXOD, поскольку при необходимости эти значения вычисляются с использованием данной формулы. В нашем примере данное поле присутствует из желания продемонстрировать требования к целостности базы данных и механизм поддержки целостности.

[7] Товар был куплен по ошибке и обменен на другой товар

[8] Обращение к этим процедурам оставлено клиентской части программы

[9] поскольку этот алиас является внешним и доступен через администратор

[10] Этот компонент представляет набор данных, построенный на базе одной физической таблицы

[11] При работе с удаленными базами данных рекомендуется использовать компонент TQuery.

[12] Внимание! При переносе кода в С++ необходимо учитывать отличия в синтаксисе, которые связаны с доступом к полям структуры, оформлением наименований полей (строки в С++ оформляются двойными кавычками, символы - одинарными) и традиционными различиями оформлений оператора присваивания.

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

 

[14] В случае программирования в среде Borland_C++ необходимо соответственно писать Table1->First, Table1->Last, Table1->Next, Table1->Prior

[15] Навигация по таблице, вообще-то говоря, разрешена лишь в определенных состояниях (например, не разрешена в режиме редактирования или вставки записи). Эти состояния можно отследить, проверяя значение свойства State. Более правильным является код:

Object_Pascal Borland_C++
procedure TForm1.NextClick(Sender: TObject); begin IF RasxodTable.State = DsBrowse THEN IF NOT RasxodTable.EOF THEN RasxodTable.Next ELSE Next.Enabled:=False; end; void __fastcall TForm1::NextClick(TObject *Sender) { if ( RasxodTable->State = DsBrowse) if (!RasxodTable->Eof) RasxodTable->Next(); ELSE Next->Enabled=False; }

В нашем случае предполагается, что в зависимости от состояния таблицы программно ограничен доступ к соответствующим навигационным кнопкам (Код блокировок доступа к кнопкам здесь не приводится).

[16] Вообще-то мы уже связывали эти таблицы, причем именно так же, при определении базы данных в WISQL. Однако смысл тех действий – определить ограничения целостности (требования правильности) БД, а смысл объявления операционной связи (Relation) – обеспечить соответствующее перемещение маркера текущей записи в таблице «Товары» при перемещении маркера текущей записи в таблице «Расход товара».

[17] Простое действие «Стоимость:=Количество*Цена», пришлось обложить проверкой условия, причем довольно деликатной:

§ Событие OnDataChange случается гораздо чаще, чем нужно выполнять интересующее нас действие.

§ Это интересующее нас действие можно выполнять, только если таблица «Расход товара» находится в состоянии, допускающем ее обновление (State=dsEdit,dsInsert).

§ Однако самое деликатное – необходимость устранить возможность рекурсивного вызова. Событие OnDataChange почти последнее в цепочке действий, реализующих последствия изменений в строке (значения поля Kolvo или KOD_TOVAR, а значит Zena). Поэтому естественно, что наше действие, изменяющее значение поля Stoim, опять инициирует эту цепочку действий...

Возможно наиболее естественным и аккуратным было бы использование события OnChange поля Kolvo и Kod_Tovar объекта RasxodTable. Но к сожалению это событие происходит раньше, чем успевает установиться новая соответствующая строка в таблице Rasxod (с соответствующей ценой) при изменении значения поля Kod_Tovar и это влечет неправильное вычисление стоимости.

[18] Другой вариант поручить этот контроль клиенту - установить свойство CustomConstraint поля Kolvo объекта RasxodTable равным Kolvo>=0, а свойство ConstraintErrorMessage равным: "Количество" должно быть неотрицательным. Заметим, что условия прописанные в процедуре вызывающуюся по событию On Validate проверяются раньше проверки условий, прописанных в условии CustomConstraint.

 

 

[19] Если эту связь определить в обратную сторону, то получим аналогичный двойственный результат, но… для правильного вычисления стоимости нам нужна связь именно та, какая определена.

[20] Как выяснилось, использовать обработчик событий DBGrid1.OnEnter для переключения таблиц необходимо с определенной долей осторожности. Этот метод отрабатывает при установке фокуса на соответствующий Grid (в частности, для Grid1 при активизации формы). Поскольку открытие базы данных и открытие формы происходит параллельно, то в зависимости от ресурсов компьютера (в частности, скорости его работы), открытие базы данных может произойти после отрабатывания данного метода. В этом случае при открытии формы выполнение оператора TovaryTable.Refresh создаст исключительную ситуацию. Обойти это можно или добавлением проверки открыта ли база данных, или использованием другого обработчика (например, OnCellClick, этот метод отрабатывает только при установки фокуса на некоторую ячейку компонента Grid мышью, правда, в этом случае возникают новые проблемы: щелчок на заголовок Grid не даст необходимого эффекта)

[21] Добавление Look-Up-поля можно делать только при закрытых таблицах.

[22] Такая реализация работы с полями выбора (LookUp-поля, DBListBox, DBComboBox, DBLookUpComboBox) может создавать побочные эффекты: Эти объекты используют таблицу в качестве списка выбора, в рассмотренном случае – это таблица TOVARY, управляемая объектом TovaryTable (типа TTable). Проблема может возникать из-за того, что с этим же объектом TovaryTable связан объект DBGrid1 (через DataSource1). Перемещение к следующей записи в DBGrid1 («Товары») одновременно означает перемещение по списку выбора... с не очень ясными последствиями... Похоже, в нашем примере побочные эффекты не наблюдаются...

Устранить возможность побочного эффекта можно, поместив на форму еще один объект типа TTable, совпадающий с TovaryTable, но с другим именем, например TovaryTCombo, и установив в LookUp-поле TOVAR значение свойства LookUpDataSet равным TovaryTCombo.

Что хорошего и плохого в том, что для управления доступом к одной и той же таблице TOVARY используется несколько объектов типа TTable? Эта ситуация означает, что мы можем открыть для доступа несколько экземпляров одной и той же таблицы – данные в них одни и те же (и вообще говоря не дублируются), но положение маркера текущей записи разное. При этом, естественно мы имеем дополнительные расходы памяти на управляющую информацию, но зато имеем возможность безопасно (и без хлопот) обрабатывать записи одной и той же таблицы одновременно по нескольким разным схемам.

 

Поделиться:





Читайте также:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...