Советы по Delphi

         

FAQ 6, Базы данных


Примечание: DataSet (набор данных) используется компонентами TTable, TQuery, и TStoredProc для связи с базой данных.

"Где мне можно получить помощь по работе с ReportSmith, InterBase и связке SQL Links/ODBC?"

Воспользуйтесь борландовским форумом разработчиков (BDEVTOOLS). В нем присутствуют группы, посвященные работе с ReportSmith, InterBase, Borland Database Engine, SQL Links/ODBC connectivity и др.

"У меня имеется TQuery и TDataSource. Cвойстве TStrings TQueryI имеет значение 'SELECT * FROM dbo.AnyTable', где dbo - база данных на моем SQL-сервере. Когда я устанавливаю свойство active в TRUE, я получаю следующую ошибку: 'Token not found. Token :dbo. line number:1'. В чем дело?"

Если свойство RequestLive установлено в True, тогда имя базы данных и таблицы необходимо указывать в кавычках:

Пример:
SELECT * FROM "dbo.table"

Если свойство RequestLive установлено в False, кавычки не нужны.

Пример:
SELECT * FROM dbo.table

"При попытке запустить приложение для работы с БД из-под Delphi, я получаю исключение EDatabaseError с сообщением 'An error occurred while attempting to initialize the Borland Database Engine (Error $2C09) (Ошибка при инициализации BDE)'."

Добавьте SHARE.EXE в ваш файл AUTOEXEC.BAT или добавьте DEVICE=VSHARE.386 в секцию [386Enh] вашего файла SYSTEM.INI и перегрузитесь.

"У меня есть Quattro Pro 6.0 и IDAPI для работы в сети. После установки Delphi и нового IDAPI поверх сетевого IDAPI и, запуская Quattro Pro с другой машины, я получаю ошибку о том, что невозможно загрузить драйвер языка (Language Driver)."

Добавьте в секцию [Borland Language Drivers] файла WIN.INI строчку, содержащую путь к каталогу IDAPI/LANGDRV. Для примера:

[Borland Language Drivers] LDPATH=C:\IDAPI\LANGDRV


"Что значит ошибка IDAPI $2C08?"

"Не могу загрузить IDAPI01.DLL". Убедитесь, что в вашем файле WIN.INI в секции, указанной ниже, в переменной DLLPATH указан корректный путь к IDAPI:

[IDAPI] DLLPATH=C:\IDAPI CONFIGFILE01=C:\IDAPI\IDAPI.CFG "Почему я получаю 'Index out of range' (индекс за пределами диапазона), когда я использую tTable.FindNearest и tTable.FindKey в таблице dBASE с выражением индекса (expression index)?"

FindKey и FindNearest не хотят работать с выражениями индексов dBASE. Для нормальной работы с выражением индексов dBASE используйте методы класса tTable GoToKey и GotoNearest.

"Какой в Delphi эквивалент Paradox TCursor?"

Компонент TTable.

"Каким образом, программным путем, создать таблицу Paradox с автоприращиваемым (Auto Increment) типом поля? Я использую TTable.CreateTable, но TFieldType не имеет этот тип полей."

Используйте TQuery или SQL-запрос CREATE TABLE. Для примера:

    procedure TForm1.Button1Click(Sender: TObject); begin with Query1 do begin DatabaseName := 'DBDemos'; with SQL do begin Clear; Add('CREATE TABLE "PDoxTbl.db" (ID AUTOINC,'); Add('Name CHAR(255),'); Add('PRIMARY KEY(ID))'); ExecSQL; Clear; Add('CREATE INDEX ByName ON "PDoxTbl.db" (Name)'); ExecSQL; end; end; end;

"Как мне узнать, какая запись и какое поле TDBGrid в данный момент являются текущими?"

Вот метод, следящий за текущими колонкой и строкой. Приведенный ниже код в методе MyDBGridDrawDataCell обновляет переменные Col и Row (которые не должны быть локальными для метода) при каждой отрисовке табличной сетки. Используя данный код, вы всегда с помощью переменных Col и Row можете узнать номер текущей колонки и строки соответственно.

    var Col, Row: Integer;
procedure TForm1.MyDBGridDrawDataCell(Sender: TObject; const Rect: TRect; Field: TField; State: TGridDrawState); var RowHeight: Integer; begin if gdFocused in State then begin RowHeight := Rect.Bottom - Rect.Top; Row := (Rect.Top div RowHeight) - 1; Col := Field.Index; end; end;

"Как мне выделить текущую строку в TDBGrid?"

В свойстве TDBGrid Options выставьте флаг dgRowSelect.

"Как мне создать маску в компоненте TDBEdit?"

Маски редактирования применимы к полям таблицы (компоненты TField), а не к управляющим элементам управления для работы с базами данных. Дважды щелкните на иконке TTable и добавьте необходимые поля таблицы. Когда поле выделено, его свойства появляются в Инспекторе Объектов, включая редактор маски. Связывание поля с TDBEdit позволяет решить эту проблему.

"Существует ли простой способ перехватывать исключения в событиях элементов управления?"

Создайте метод формы для перехвата исключений. Данный метод вызывает метод приложения (объект Application) OnException. В нем необходимо производить проверку на необходимые вам исключения, например, EDatabaseError. Откройте файл справки и посмотрите информацию о событии OnException. Там ясно и достаточно подробно описан процесс создания обработчика данного события. Для примера:

    Procedure TForm1.MyExcept(Sender:TObject; E:Exception); {Не забудьте предварительно объявить класс} begin If E is EDatabaseError then MessageDlg('Перехвачено исключение', mtInformation, [mbOk], 0) else { если эта не та ошибка, которую вы ищите, передайте объект исключительной ситуации дальше } end;
procedure TForm1.FormCreate(Sender: TObject); begin Application.OnException := MyExcept; { Мы только что назначили обработчика события OnException } end;

"Какие версии Informix (Online, I-NET) поддерживают SQL Links?"

SQL Links прекрасно работает со всеми версиями Informix sever, включая версию 5.01. Тем не менее она не совместима с новыми версиями их клиентов (клиент Windows версии 5.0). В случае клиентов вам надлежит пользоваться клиентом версии 4.2 (для DOS).

"Вы можете дать определение 'IDAPI'? What is 'SQL Links'?"

IDAPI - Integrated Database Application Program Interface (интегрированный программный интерфейс приложений баз данных). BDE позволяет получить доступ к множеству источников данных с последовательным API. IDAPI - именно API для BDE. Оно включает в себя все функции, необходимые для получения доступа, манипуляции данными и т.п.: Delphi, dBASE for Windows и Paradox for Windows пользуются данными функциями для получения доступа к данным: вы также можете использовать их в своих программах. С приобретением BDE вы также получаете всю необходимую документацию. В ней содержится перечень всех доступных функций с описанием того, что они делают. Если вы посмотрите исходные файлы Delphi, вы увидите как используются там эти функции. Названия функций имеют префикс "Dbi" (например, DbiCreateTable).

SQL Links - это коллекция нативных драйверов, позволяющих вам подключаться к удаленным серверам баз данных.

"IDAPI необходим для доступа к данным в Delphi? Может быть как-то 'упрятать' IDAPI внутрь Delphi EXE-файла, чтобы не устанавливать IDAPI на каждом компьютере пользователя вдобавок к моей программе?"

IDAPI необходим для доступа к данным в Delphi. Delphi поставляется с дискетой, содержащей установку IDAPI.

"Как мне изменить цвет ячейки TDBGrid?"

Создайте следующий обработчик события компонента TDBGrid OnDrawDataCell:

    Procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const Rect: TRect; Field: TField; State: TGridDrawState); begin If gdFocused in State then with (Sender as TDBGrid).Canvas do begin Brush.Color := clRed; FillRect(Rect); TextOut(Rect.Left, Rect.Top, Field.AsString); end; end;

Установите отрисовку по умолчанию в True. Только таким способом можно нарисовать отдельную ячейку. Если же вы установили DefaultDrawing в False, вам необходимо будет самим отрисовать все ячейки, используя свойства canvas.

"Каким образом можно недопустить появление диалогового окна с запросом пароля, если я открываю таблицу, защищенную паролем?"

Просто обеспечьте объект Session паролем, который необходимо ввести для открытия таблицы:

    Session.AddPassword ('PASSWORD');

При закрытии таблицы вы можете удалить пароль с помощью команды RemovePassword('PASSWORD'), или удалить все текущие пароли с помощью RemoveAllPasswords. (Примечание: все вышесказанное относится только к таблицам Paradox)

"Где мне найти список и описание функций BDE и типов данных?"

DBIPROCS.INT в вашем каталоге DELPHI\DOC\ содержит список функций BDE, ожидаемые параметры, возвращаемые значения и их краткое описание. DBITYPES.INT - список типов, используемых функциями BDE. Для вызова любой из функций BDE необходимо наличие следующих модулей с списке USES: DBITYPES, DBIPROCS и DBIERRS. Для получения более подробной информации по использованию функций IDAPI, закажите руководство "Database Engine User's guide" в службе поддержки пользователей.

"Может ли BDE API или другие доступные DLL воссоздавать разрушенные индексы (типа TUTILITY.EXE, поставляемое с Pdoxwin)?"

Функция BDE для воссоздания индексов называется DbiRegenIndexes().

Добавьте следующие модули в секцию USES: DBITYPES, DBIPROCS и DBIERRS. Затем вы можете вызвать функцию следующим образом:

    DBIRegenIndexes(Table1.Handle);

Примечание: Таблица должна быть открыта в эксклюзивном режиме и индекс должен уже существовать.

"Может ли BDE API или другие доступные DLL паковать таблицы dBASE?"

Функция BDE для пакования таблиц dBASE называется DbiPackTable().

Добавьте следующие модули в секцию USES: DBITYPES, DBIPROCS и DBIERRS. Затем вы можете вызвать функцию следующим образом:

    DBIPackTable(Table1.DbHandle, Table1.Handle, 'TABLENAME.DBF', szDBASE, TRUE);

Примечание: Таблица должна быть открыта в эксклюзивном режиме.

"Возможно ли программным путем добавить псевдоним к файлу IDAPI.CFG?"

Функция BDE, осуществляющее это, называется DbiAddAlias(). Спецификация доступна в Section 6 (Database) библиотеки, файл AddAlias.txt. Также в Section 6 (Database) библиотеки доступен компонент AliasManager. С помощью него возможно создание, удаление и редактирование псевдонимов.

"Как мне посмотреть dBASE-записи, помеченные для удаления?"

В обработчике события AfterOpen вызовите нижеприведенную функцию. Вы также должны включить DBITYPES, DBIERRS, DBIPROCS в список используемых модулей. При вызове функции укажите в качестве аргументов TTable и TRUE/FALSE для показа/скрытия удаленных записей. Например:

    procedure TForm1.Table1AfterOpen(DataSet: TDataset); begin SetDelete(Table1, TRUE); end;
procedure SetDelete(oTable:TTable; Value: Boolean); var rslt: DBIResult; szErrMsg: DBIMSG; begin try oTable.DisableControls; try rslt := DbiSetProp(hDBIObj(oTable.Handle), curSOFTDELETEON, LongInt(Value)); if rslt <> DBIERR_NONE then begin DbiGetErrorString(rslt, szErrMsg); raise Exception.Create(StrPas(szErrMsg)); end; except on E: EDBEngineError do ShowMessage(E.Message); on E: Exception do ShowMessage(E.Message); end; finally oTable.Refresh; oTable.EnableControls; end; end;

"Как в табличной сетке создать колонку, куда будут заноситься записи из таблицы dBASE, помеченных для удаления?"

Создайте вычисляемые поля, затем в обработчике события таблицы OnCalcField замените вычисляемые поля, которые вы создали, следующим образом:

    procedure TForm1.Table1CalcFields(DataSet: TDataset); var RCProps : RecProps; Result : DBIResult; begin Result := DbiGetRecord(Table1.Handle, dbiNoLock, Nil, @RCProps); If RCProps.bDeleteFlag then Table1Del.Value := 'X' else Table1Del.Value := ''; end;

Примечание: Сначала вы должны вызвать функцию SetDelete(TTable,TRUE), описанную в предыдущем вопросе.

"Как мне определить реальный размер blob-поля, хранящегося в таблице?"

Здесь приведена функция GetBlobSize, возвращающая размер заданного blob, memo, или графического поля. Пример вызова функции:

    Function GetBlobSize(Field: TBlobField): Longint; begin with TBlobStream.Create(Field, bmRead) do try Result := Seek(0, 2); finally Free; end; end;
procedure TForm1.Button1Click(Sender: TObject); begin { Поле редактирования Edit1 будет показывать } { размер memo-поля с именем Notes.           } Edit1.Text := IntToStr(GetBlobSize(Notes)); end;

"Как мне показать содержимое memo-поля в DBGrid?"

Используйте расположенный ниже код в обработчике события OnDrawDataCell компонента DBGrid. Примечание: перед созданием объекта TMemoField дважды щелкните на компоненте TTable и добавьте memo-поле.

    procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const Rect: TRect; Field: TField; State: TGridDrawState); var P  : array [0..50] of char;   {размер массива - необходимое число символов} BS : tBlobStream;             {из memo-поля} S  : String; begin If Field is TMemoField then begin with (Sender as TDBGrid).Canvas do begin {Table1Notes - TMemoField} BS := tBlobStream.Create(Table1Notes, bmRead); FillChar(P,SizeOf(P),#0);               {строка с терминатором} BS.Read(P, 50);           {читаем 50 символов из memo в blobStream} BS.Free; S := StrPas(P); while Pos(#13, S) > 0 do              {удаляем символы перевода строки и} S[Pos(#13, S)] := ' ';              {пропуска страницы} While Pos(#10, S) > 0 do S[Pos(#10, S)] := ' '; FillRect(Rect);                {очищаем ячейку} TextOut(Rect.Left, Rect.Top, S);         {заполняем ячейку данными memo} end; end; end;

"Существует ли способ для ввода данных использовать клавишу Enter вместо Tab или клавиши мыши?"

Используйте приведенный код в обработчике события компонента Edit OnKeyPress.

    procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char); begin If Key = #13 Then Begin SelectNext(Sender as tWinControl, True, True ); Key := #0; end; end;

Это заставляет клавишу Enter вести себя подобно клавише Tab. После этого выберите на форме все элементы управления (за исключением кнопок), у которых вы хотели бы видеть такую функциональность, и в Инспекторе Объектов сошлитесь в обработчике события OnKeyPress на созданную процедуру EditKeyPress. Теперь каждый компонент, который вы выбрали, будет воспринимать клавишу Enter как Tab. Если вы хотели видеть данную функциональность на уровне формы (по сравнению с элементами управлениями), сотрите в Инспекторе Объектов у всех элементов обработчики событий OnKeyPress и сошлитесь на созданную процедуру EditKeyPress в обработчике OnKeyPress _формы_. Затем измените Sender на ActiveControl и установите свойство формы KeyPreview в True:

    procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char); begin If Key = #13 Then begin SelectNext(ActiveControl as tWinControl, True, True ); Key := #0; end; end;

Это позволит любому элементу на форме (по возможности) воспринимать клавишу Enter как клавишу Tab.

"Как мне воспользоваться функцией locate в неиндексированном поле?"

Ниже приводится код функции, которую вы можете разместить в вашем модуле и вызывать следующим образом:

    Locate(Table1, Table1LName, 'Beman');

Table1 - компонент Table, Table1LName - TField, добавляемое вами с помощью редактора полей (двойной щелчок на компоненте Table) и 'Beman' - имя, которое вы хотите найти.

    (* Функция Locate находит SValue в неиндексированной таблице *) Function Locate( const oTable: TTable; const oField: TField; const sValue: String): Boolean; var bmPos  : TBookMark; bFound : Boolean; begin Locate := False; bFound := False; If not oTable.Active then Exit; If oTable.FieldDefs.IndexOf( oField.FieldName ) < 0 then Exit; bmPos := oTable.GetBookMark; With oTable do begin DisableControls; First; While not EOF do if oField.AsString = sValue then begin Locate := True; bFound := True; Break; end else Next; end ; If (Not bFound) then oTable.GotoBookMark( bmPos); oTable.FreeBookMark( bmPos ); oTable.EnableControls; end;

"Почему я не могу использовать опцию ixUnique при создании индекса таблицы Paradox с помощью метода AddIndex компонента TTable?"

Опции индекса, используемые методом AddIndex компонента TTable, чуствительны к типу таблицы. Для примера, опция ixUnique работает с таблицами dBASE, но не с таблицами Paradox. Следующая табличка показывает какие опции могут быть применены к каким таблицам (dBASE или Paradox).

Опции индекса dBASE Paradox --------------------------------------- ixUnique * ixDescending * * ixNonMaintained * * ixPrimary * ixCaseInsensitive * "Как мне определить номер записи набора данных?"

Если набор данных связан с таблицей Paradox или dBASE, то для определения номера записи достаточно пары вызовов BDE (как показано ниже). BDE не поддерживает нумерацию записей для набора данных, базирующегося на таблицах SQL, поэтому, если ваш сервер сам поддерживает нумерацию записей, то для выяснения способа ее получения, вам необходимо обратиться к его документации.

Приведенная ниже функция в качестве параметра берет любой компонент, производный от TDataset (например, TTable, TQuery, TStoredProc) и возвращает номер текущей записи (больший чем ноль), если это таблица Paradox или dBASE. В противном случае функция возвращает ноль.

Примечание: для таблиц dBASE функция всегда возвращает физический номер записи. Так, если ваш набор данных - TQuery, или вами в наборе данных установлен диапазон (фильтр), то возвращаемый номер записи не обязательно может относиться к заданному набору данных, а может отражать физическое расположение записи в таблице dBASE.

    uses DbiProcs, DbiTypes, DBConsts;
function RecordNumber(Dataset: TDataset): Longint; var CursorProps: CurProps; RecordProps: RECProps; begin { Возвращает 0, если набор данных связан не с Paradox или dBASE } Result := 0;
with Dataset do begin { набор данных активен? } if State = dsInactive then DBError(SDataSetClosed);
{ Данный вызов необходим для получения курсора iSeqNums } Check(DbiGetCursorProps(Handle, CursorProps));
{ Синхронизируем курсор BDE с курсором Dataset } UpdateCursorPos;
{ Заполняем RecordProps свойствами текущей записи } Check(DbiGetRecord(Handle, dbiNOLOCK, nil, @RecordProps));
{ какого типа наш набор данных? } case CursorProps.iSeqNums of 0: Result := RecordProps.iPhyRecNum;  { dBASE   } 1: Result := RecordProps.iSeqNum;     { Paradox } end; end; end;

"При редактировании записи я получаю сообщение DBEngine, гласящее "Multiple records found but only one expected" (обнаружены многичисленные записи в то время как ожидалась одна). Что это значит?

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

"Мой опыт обращения к данным Microsoft Access через Delphi-компоненту TTable успешным не назовешь. Используя TQuery я смог получить для работы только вид с флажком "только для чтения", но вид с возможностью чтения/записи получить не удалось. После диалога авторизации я получил сообщение об исключительной ситуации типа 'Passthrough SQL connection must be shared'."

Используйте Database Engine Configuration для изменения опции 'SQLPASSTHRU MODE' в псевдониме, связанным с вашей базой данных Access, из пустого значения по умолчанию на 'SHARED AUTOCOMMIT' (без кавычек).

"Как узнать, что текущая запись в наборе данных изменилась?"

Проверьте в обработчике события OnDataChanged компонента DataSource значение свойства State. В случае изменения текущей записи свойство State будет иметь значение dsBrowse. Следующий пример выводит информационное окошко при каждом изменении записи в источнике данных MyDataSource:

    procedure TMyForm.MyDataSourceDataChange(Sender: TObject; Field: TField); begin if (Sender as TDataSource).State = dsBrowse then ShowMessage('Позиция записи изменилась'); end;

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

    NewTable.IndexDefs.Assign(Table1.IndexDefs)
?

Вы правильно передали определение индекса в NewTable, но тем не менее свойство IndexDefs Table1 следует обновить, для этого существует метод Update, как им пользоваться видно из примера:

    with NewTable do begin Active := False; DatabaseName := 'DBDEMOS'; TableName := 'Temp'; TableType := ttParadox; FieldDefs.Assign(Table1.FieldDefs); Table1.IndexDefs.Update;             { В первую очередь обновляем } IndexDefs.Assign(Table1.IndexDefs); CreateTable; end;
[000582]



Содержание раздела