|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Базы данныхСозданиеСоздание db-файла во время работы приложенияuses DB, DBTables, StdCtrls; procedure TForm1.Button1Click(Sender: TObject); var tSource, TDest: TTable; begin TSource:= TTable.create(self); with tsTSource do begin DatabaseName:= 'dbdemos'; TableName:= 'customer.db'; open; end; TDest:= TTable.create(self); with TDest do begin DatabaseName:= 'dbdemos'; TableName:= 'MyNewTbl.db'; FieldDefs.Assign(TSource.FieldDefs); IndexDefs.Assign(TSource.IndexDefs); CreateTable; end; TSource.close; end; ДоступОчень медленный доступ к таблице при первом обращенииДанная проблема возникает из-за того, что BDE вначале запрашивает базу данных для получения информации о таблице, прежде чем он начнет с ней работать. Как только появляется информация о таблице, она кэшируется и обращение к таблице во время всего сеанса (пока TDatabase.Connection имеет значение True) происходит практически мгновенно. Для того, чтобы использовать кэшируемую информацию и при последующем запуске приложения, в конфигурации BDE найдите необходимый псевдоним и установите BDE CACHE = TRUE и BDE CACHE DIR = 'C:\temp' или любой другой удобный каталог.
ПоискПоиск величины при вводеКаким способом можно производить поиск подходящих величин в момент ввода? Табличный курсор (визуально) должен перемещаться к наиболее подходящему значению при добавлении пользователем новых символов водимой величины. Первоначально код писался под Delphi 1. Это может и не лучшее решение, но это работает. Для поиска величины таблица держится открытой. Индекс должен, естественно, принадлежать полю, используемому элементом управления EditBox. В случае изменения содержимого EditBox, новое значение используется для вызова стандартной функции FindNearest таблицы TTable. Возвращаемая величина снова присваивается свойcтву Text элемента EditBox. Я привел лишь общее решение задачи. Фактически во время изменения значения я включал таймер на период 1/3 секунды и в обработчике события OnTimer проводил операцию поиска (с выключением таймера). Это позволяло пользователю набирать без задержки нужный текст без необходимости производить поиск в расчете на вновь введенный символ (поиск проводился только при возникновении задержки в 1/3 секунды). Вам также может понадобиться специальный обработчик нажатия клавиши backspace или добавления символа в любое место строки. Вместо возвращения результатов элементу EditBox (который перезаписывает введенное пользователем значение), вы можете передавать результаты другому элементу управления, например компоненту ListBox. Вы также можете отобразить несколько наиболее подходящих значений, к примеру так: procedure Edit1OnChange(…); var i:integer; begin if not updating then exit; {сделайте обновление где-нибудь еще – например при срабатывании таймера} updating:= false; Table1.FindNearest([Edit1.text]); ListBox1.clear; i:= 0; while (i < 5) and (not (table1.eof)) do begin listbox.items.add(Table1.fields[0].asString); inc(i); table1.next; end; listbox1.itemindex:= 0; end; Быстрый поиск в базах данныхЯ представляю на Ваш суд утилиту быстрого поиска по базе данных. Данная технология производит поиск по полям, преобразуя их значения в строки (все значения преобразуются в верхний регистр, включая действительные числа). Данное решение может быть не самым быстрым, однако на поверку оно оказывается быстрее остальных, обнаруженных мною в Интернете (может вам повезет больше). Более того, представьте, что действительное значение какого-либо поля равно 4.509375354, а значение поиска равно 7, в этом случае утилита засчитает "попадание". Утилита удобна также тем, что она за один проход производит поиск более, чем в одном поле. Это удобно, если у Вас имеются, к примеру, два поля с адресами. Это моя первая "серьезная" разработка, так как первое, с чем я столкнулся, изучая Delphi, стала необходимость включения процедуры поиска в любое приложение, работающее с базой данных. А так как поиск – вещь тоже сугубо специфическая, как и любое приложение, то мне пришлось побороть свой страх перед "крутым программированием" и попробовать написать свой поисковый механизм, удовлетворивший меня (и, надеюсь, других) своей скоростью и возможностью "мульти"-поиска по нескольким полям. Я надеюсь, что он поможет тем программистам, кто часто сталкивается с подобными задачами. Технология довольно легка для понимания, но если у Вас возникли какие-либо вопросы, пошлите мне письмо электронной почтой, я буду рад Вам помочь. Посмотрев код, можно легко узнать поддерживаемые типы полей (добавить новые не составит проблем). Если кто-либо обнаружит ошибочный код или расширит функциональность утилиты, пожалуйста, пошлите это мне, я буду весьма благодарен. Спасибо. unit Finder; interface uses DB, DBTables, SysUtils; function GrabMemoFieldAsPChar(TheField: TMemoField): PChar; function DoFindIn(TheField: TField; SFor: String): Boolean; function FindIt(TheTable : TDataSet; TheFields: array of integer; SearchBackward: Boolean; FromBeginning: Boolean; SFor: String): boolean; {применение функции FindIt – if FindIt(NotesSearchT, [NotesSearchT.FieldByName('Leadman').Index], False, True, SearchText.Text) then DoSomething; } implementation function GrabMemoFieldAsPChar(TheField: TMemoField): PChar; begin with TBlobStream.Create(TheField, bmRead) do begin GetMem(Result, Size + 1); FillChar(Result^, Size + 1, #0); Read(Result^, Size); Free; end; end; function DoFindIn(TheField : TField; SFor : String): Boolean; var PChForMemo: PChar; begin Result:= False; case TheField.DataType of ftString: begin if (Pos(SFor, UpperCase(TheField.AsString))> 0) then Result := True; end; ftInteger: begin if (Pos(SFor, TheField.AsString)> 0) then Result:= True; end; ftBoolean: begin if SFor = UpperCase(TheField.AsString) then Result:= True; end; ftFloat: begin if (Pos(SFor, TheField.AsString) > 0) then Result := True; end; ftCurrency: begin if (Pos(SFor, TheField.AsString) > 0) then Result := True; end; ftDate..ftDateTime: begin if (Pos(SFor, TheField.AsString) > 0) then Result := True; end; ftMemo: begin SFor[Ord(SFor[0]) + 1]:= #0; PChForMemo:= GrabMemoFieldAsPChar(TMemoField(TheField)); StrUpper(PChForMemo); if not (StrPos( PChForMemo, @SFor[1] ) = nil) then Result:= True; FreeMem(PChForMemo, StrLen(PChForMemo + 1)); end; end; end; function FindIt(TheTable: TDataSet; TheFields: array of integer; SearchBackward: Boolean; FromBeginning: Boolean; SFor: String): boolean; var i, HighTheFields, LowTheFields: integer; BM: TBookmark; begin TheTable.DisableControls; BM:= TheTable.GetBookmark; try LowTheFields:= Low(TheFields); HighTheFields:= High(TheFields); SFor:= UpperCase(SFor); Result:= False; if FromBeginning then TheTable.First; if SearchBackwardthen begin TheTable.Prior; while not TheTable.BOF do begin for i:= LowTheFields to HighTheFields do begin if DoFindIn(TheTable.Fields[TheFields[i]], SFor) then begin Result := True; Break; end; end; if Result then Break else TheTable.Prior; end; end else begin TheTable.Next; while not TheTable.EOF do begin for i:= LowTheFields to HighTheFields do begin if DoFindIn(TheTable.Fields[TheFields[i]], SFor) then begin Result:= True; Break; end; end; if Result then Break else TheTable.Next; end; end; finally TheTable.EnableControls; if not Result then TheTable.GotoBookmark(BM); TheTable.FreeBookmark(BM); end; end; end. КалькуляцияХитрость OnCalcFieldsСобытие OncalcFields генерится ОЧЕНЬ часто и может быть необязательным и занимать большое количество времени, например, у вас есть таблица с неким вычисляемым полем, и при каждом редактировании таблицы вызывается следующий код: MyCalcField.AsInteger:= Table1Field1.AsInteger + 10; Теперь, если Вы решили пройти последовательно каждую запись огромной таблицы, вы можете представить, какое количество таких событий будет сгенерировано! Они будут необязательны в случае, если вы сделаете обработку полей в отдельной процедуре. Мой совет следующий: выключите генерацию события OnCalcFields, обработайте все поля и снова включите генерацию данного события, к примеру так: Procedure TForm1.BigProcessingFunction; begin Table1.OnCalcFields:= nil; <Включите любые по сложности вычисления в этом месте!> Table1.OnCalcFields:= Table1OnCalcFields; end; Поля не вычисляются в течение времени обработки, которое может быть достаточно велико, но при наличие громоздких вычислений специфического поля (или даже нескольких полей), все вычисляется за один проход! Данный метод позволяет исключить необязательный код и может быть использован повсюду, где применяются большие таблицы или сложный алгоритм калькуляции поля. Разница в скорости обработки таблицы довольно ощутима. dBASEТаблицы dBASE: Структура .DBF-файлаИногда возникает необходимость поработать с таблицей dBASE напрямую, без Borland Database Engine (BDE). К примеру, если .DBT-файл (содержащий MEMO-данные) для данной таблицы безвозвратно потерян, .DBF-файл становится абсолютно непригодным, поскольку байт в заголовке .DBF-файла указывает, что таблица должна содержать соответствующий MEMO-файл. Решение этой проблемы потребует обнуление этого байта, для того чтобы таблица не указывала на сопутствующий MEMO-файл. Или, если Вам захотелось написать собственную программу для работы с данными. Ниже приводяся структуры .DBF-файлов для таблиц dBASE. Представлены структуры файлов для различных версий dBASE: dBASE III PLUS 1.1, dBASE IV 2.0, dBASE 5.0 под DOS и dBASE 5.0 для Windows. Структура заголовка файла данных для таблицы dBASE III PLUS.
n – последний байт массива с описаниями полей. Размер массива зависит от количества полей в табличном файле.
Записи в табличном файле располагаются непосредственно за заголовком таблицы. Данным записи предшествует байт, указывающий на удаленность записи: значение 20h (пробел) указывает что запись не удалена, значение 2Ah (звездочка) – запись была удалена. Поля упаковываются записями без разделителей полей или терминаторов записи. Конец файла помечается единственным байтом (с EOF-маркером), OEM-код которого соответствует значению 26 (1Ah). Вы можете ввести данные в кодовой странице OEM как показано ниже.
MEMO-поля хранят данные в .DBT-файлах, состоящих из перечисляемых последовательных блоков (0, 1, 2 и т.д.). Размер блока равен 512 байт. Первый блок в .DBT-файле (нулевой блок) – заголовок .DBT-файла. MEMO-поле каждой записи .DBF-файла содержит номер (значение указывается в кодовой странице OEM), указывающий на блок с хранимыми данными. Если поле не содержит никаких данных, .DBF-файл будет заполнен пробелами (20h) (а не числами). В случае изменения данных какого-либо поля, блоки могут изменить свои порядковые номера для отображения новой позиции данных в .DBT-файле. Данная информация взята из руководства по использованию dBASE III Plus ("Using dBASE III PLUS", Appendix C). Структура заголовка файла данных для таблицы dBASE IV 2.0.
n - последний байт массива с описаниями полей. Размер массива зависит от количества полей в табличном файле.
Записи в табличном файле располагаются непосредственно за заголовком таблицы. Данным записи предшествует байт, указывающий на удаленность записи: значение 20h (пробел) указывает что запись не удалена, значение 2Ah (звездочка) – запись была удалена. Поля упаковываются записями без разделителей полей или терминаторов записи. Конец файла помечается единственным байтом (с EOF-маркером), OEM-код которого соответствует значению 26 (1Ah).
MEMO-поля хранят данные в .DBT-файлах, состоящих из перечисляемых последовательных блоков (0, 1, 2 и т.д.). Переменная BLOCKSIZE определяет размер каждого блока. Первый блок в .DBT-файле (нулевой блок) – заголовок .DBT-файла. MEMO-поле каждой записи .DBF-файла содержит номер (значение указывается в кодовой странице OEM), указывающий на блок с хранимыми данными. Если поле не содержит никаких данных, .DBF-файл будет заполнен пробелами (20h) (а не числами). В случае изменения данных какого-либо поля, блоки могут изменить свои порядковые номера для отображения новой позиции данных в .DBT-файле. Данная информация взята из справочника по dBASE IV ("dBASE IV Language Reference", Appendix D). Структура заголовка файла данных для таблицы dBASE 5.0 под DOS.
n - последний байт массива с описаниями полей. Размер массива зависит от количества полей в табличном файле.
Записи в табличном файле располагаются непосредственно за заголовком таблицы. Данным записи предшествует байт, указывающий на удаленность записи: значение 20h (пробел) указывает что запись не удалена, значение 2Ah (звездочка) – запись была удалена. Поля упаковываются записями без разделителей полей или терминаторов записи. Конец файла помечается единственным байтом (с EOF-маркером), OEM-код которого соответствует значению 26 (1Ah). Вы можете ввести данные в кодовой странице OEM как показано ниже.
MEMO-поля хранят данные в .DBT-файлах, состоящих из перечисляемых последовательных блоков (0, 1, 2 и т.д.). Переменная BLOCKSIZE определяет размер каждого блока. Первый блок в .DBT-файле (нулевой блок) – заголовок .DBT-файла. MEMO-поле каждой записи .DBF-файла содержит номер (значение указывается в кодовой странице OEM), указывающий на блок с хранимыми данными. Если поле не содержит никаких данных, .DBF-файл будет заполнен пробелами (20h) (а не числами). В случае изменения данных какого-либо поля, блоки могут изменить свои порядковые номера для отображения новой позиции данных в .DBT-файле. Если вы удаляете текст в МЕМO-поле, в отличие от dBASE III PLUS, таблица dBASE 5.0 под DOS для ввода нового текста использует удаленную область. dBASE III PLUS всегда добавляет новый текст в конец .DBT-файла. В dBASE III PLUS размер .DBT-файла растет всякий раз при добавления нового текста, даже если перед этим текст был удален. Данная информация взята из справочника по dBASE под DOS ("dBASE for DOS Language Reference manual", Appendix C). Структура заголовка файла данных для таблицы dBASE 5.0 под Windows.
n - последний байт массива с описаниями полей. Размер массива зависит от количества полей в табличном файле.
Записи в табличном файле располагаются непосредственно за заголовком таблицы. Данным записи предшествует байт, указывающий на удаленность записи: значение 20h (пробел) указывает что запись не удалена, значение 2Ah (звездочка) – запись была удалена. Поля упаковываются записями без разделителей полей или терминаторов записи. Конец файла помечается единственным байтом (с EOF-маркером), OEM-код которого соответствует значению 26 (1Ah). Вы можете ввести данные в кодовой странице OEM как показано ниже.
Бинарные, MEMO и OLE-поля хранят данные в .DBT-файлах, состоящих из перечисляемых последовательных блоков (0, 1, 2 и т.д.). Переменная BLOCKSIZE определяет размер каждого блока. Первый блок в .DBT-файле (нулевой блок) – заголовок .DBT-файла. Бинарное, OLE– или MEMO-поле каждой записи .DBF-файла содержит номер (значение указывается в кодовой странице OEM), указывающий на блок с хранимыми данными. Если поле не содержит никаких данных, .DBF-файл будет заполнен пробелами (20h) (а не числами). В случае изменения данных какого-либо поля, блоки могут изменить свои порядковые номера для отображения новой позиции данных в .DBT-файле. Если вы удаляете текст в бинарном, OLE– или МЕМO-поле, в отличие от dBASE III PLUS и dBASE IV, таблица dBASE 5.0 под Windows для ввода нового текста использует удаленную область. dBASE III PLUS всегда добавляет новый текст в конец .DBT-файла. В dBASE III PLUS размер .DBT-файла растет всякий раз при добавления нового текста, даже если перед этим текст был удален. Данная информация взята из справочника по dBASE под Windows ("dBASE for Windows Language Reference manual", Appendix C).
РазноеСканирование версии структуры базы данныхСпасибо за идеи, высказанные в группах новостей и присланные по электронной почте. Я думаю, что нашел лучшее решение. Очевидно, BDE содержит номер версии структуры, по крайней мере для файлов Paradox. (Я не могу поручиться за dBase и другие форматы.) Всякий раз при изменении структуры (например, в Database Desktop) BDE увеличивает номер версии. Следующий модуль содержит функцию, которая возвращает версию структуры базы данных: (***************************************************************************** * DbUtils.pas * * Утилита для работы с базами данных * * Создана 09/20/96 *****************************************************************************) unit Dbutils; (****************************************************************************) (****************************************************************************) interface (****************************************************************************) (****************************************************************************) uses DbTables; function DbGetVersion(table: TTable): LongInt; (****************************************************************************) (****************************************************************************) implementation (****************************************************************************) (****************************************************************************) uses Db, DbiProcs, DbiTypes, {DbiErrs,} SysUtils; {---------------------------------------------------------------------------} (* * Цель: определение номера версии структуры таблицы * Параметры: table (I) – интересующая нас таблица * Возвращаемая величина: номер версии * Исключительная ситуация: EDatabaseError *) function DbGetVersion(table: TTable): LongInt; var hCursor : hDBICur; tableDesc: TBLFullDesc; cName : array[0..255] of char; begin { копируем имя таблицы в строку 'с' } StrPCopy(cName, table.TableName); { просим BDE создать запись, содержащую информацию об определенной таблице } Check(DbiOpenTableList(table.DBHandle, True, False, cName, hCursor)); { получаем запись, содержащую информацию о структуре } Check(DbiGetNextRecord(hCursor, dbiNOLOCK, @tableDesc, nil)); { возвращаем поле записи, содержащее номер версии структуры нашей таблицы } Result:= tableDesc.tblExt.iRestrVersion; Check(DbiCloseCursor(hCursor)); end; end. Перемещение таблицЗдесь я привожу примеры программ, которые я использую для копирования и удаления таблиц. Необходимые для работы модули: DB, DBTables, DbiProcs,DbiErrs, и DbiTypes. Вам всего лишь необходимо указать каталог расположения, исходное имя таблицы, каталог назначения и имя таблицы, куда будет скопирована исходная таблица и BDE скопирует таблицу целиком со всеми индексами. Процедура удаления в качестве входных параметров использует каталог расположения и имя таблицы, при этом BDE удаляет как саму таблицу, так и все файлы, связанные с ней (индексы и т.п.). Для тестирования данные процедуры были помещены в новое приложение и мне пришлось их немного отредактировать, чтобы удалить некоторые зависимости, которые были связаны с главной формой приложения. Теперь процедуры являются полностью автономными и могут быть помещены в отдельный модуль. (Не забудьте включить его в список используемых модулей). Пользуйтесь на здоровье! procedure TConvertForm.CopyTable(FromDir, SrcTblName, ToDir, DestTblName: String); var DBHandle: HDBIDB; ResultCode: DBIResult; Src, Dest, Err: Array[0..255] of char; SrcTbl, DestTbl: TTable; begin SrcTbl:= TTable.Create(Application); DestTbl:= TTable.Create(Application); try SrcTbl.DatabaseName:= FromDir; SrcTbl.TableName:= SrcTblName; SrcTbl.Open; DBHandle:= SrcTbl.DBHandle; SrcTbl.Close; ResultCode:= DbiCopyTable(DBHandle,false, StrPCopy(Src,FromDir + '\' + SrcTblName), nil, StrPCopy(Dest,ToDir + '\' + DestTblName)); if (ResultCode <> DBIERR_NONE) then begin DbiGetErrorString(ResultCode,Err); raise EDatabaseError.Create('При копировании ' + FromDir + '\' + SrcTblName + ' в ' + ToDir + '\' + DestTblName + ' ,' + 'BDE сгенерировал ошибку ''' + StrPas(Err) + ''''); end; finally SrcTbl.Free; DestTbl.Free; end; end; procedure TConvertForm.DeleteTable(Dir, TblName: String); var DBHandle: HDBIDB; ResultCode: DBIResult; tbl, Err: Array[0..255] of char; SrcTbl, DestTbl: TTable; SrcTbl:= TTable.Create(Application); try SrcTbl.DatabaseName:= Dir; SrcTbl.TableName:= TblName; SrcTbl.Open; DBHandle:= SrcTbl.DBHandle; SrcTbl.Close; ResultCode:= DbiDeleteTable(DBHandle, StrPCopy(Tbl,Dir + '\' + TblName), nil); if (ResultCode <> DBIERR_NONE) then begin DbiGetErrorString(ResultCode,Err); raise EDatabaseError.Create('Удаляя ' + Dir + '\' + TblName + ', BDE ' + 'сгенерировал ошибку ''' + StrPas(Err) + ''''); end; finally SrcTbl.Free; end; end; Прокрутка таблицы: хитрость PeekMessage()На днях я решил поиграть с API-функцией PeekMessage(). Функция работает, но ловить ее нужно следующим образом. Я прокручиваю таблицу, связанную с набором данных. "Поиск" в наборе данных замедляет процесс скролирования (условимся называть "поиском" синхронное перемещение табличного курсора в процессе скроллирования, при котором текущей записью становится запись, ближайшая к нажимаемой кнопке полосы прокрутки). Возникла задача: необходимо отменить "поиск" (процесс слежения) и переместить указатель на необходимую запись только в случае остановки пользователем процесса скроллирования, другими словами – пока пользователь осуществляет скроллирование, "поиск" необходимо отменить. Итак, ко мне в голову пришла мысль, что с помощью PeekMessage() можно выловить определенное сообщение и тем самым отменить поиск во время прокрутки. Звучит просто, но на самом деле все оказалось наоборот. Я установил фильтр поиска сообщений на WM_MOUSEFIRST/LAST. Ситуация: пользователь непрерывно прокручивает DBGrid вниз, т.е. держит нажатой нижнюю кнопку скроллирования. В результате PeekMessage() возвращает False – нас это не устраивает, это не то, что мы хотим. Положительный результат можно получить только в случае сверхскоростных манипуляций мышью. Если в фильтре использовать 0 и 0, чтобы поймать любое сообщение, результат всегда будет True. Причина, очевидно в том, что любой щелчок мыши в области DBGrid никак не обойдется без последствий, генерация системой сообщения PAINT яркий тому пример, поэтому PeekMessage может возвратить True в любое время, что тоже не может нам помочь. Было бы хорошо, если бы дескриптор DBGrid получал событие OnMouseUp() во время его скроллирования. Обидно, но OnMouseUp() работает только с DBGrid, а не с полосами прокрутки. OnMouseUp() с TForm при KeyPreview:=true не работает, я проверял. После пришла идея опросить состояние кнопок мыши с помощью функции GetKeyState(). Пока кнопка нажата (DOWN), "поиск" запрещен, и наоборот. UP (кнопка отжата) свидетельствует об окончании процесса скроллирования. Данный способ работы с окном во время манипуляций с его полосой прокрутки заработал без проблем. Теперь все в порядке: поиска во время прокрутки не происходит и табличный курсор также никуда не перемещается. Рассмотренная тема имеет отношение к полосам прокрутки, а события OnKeyUp() и OnMouseUp() могут применяться где-нибудь еще. |
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|