Валентин Озеров - Советы по Delphi. Версия 1.0.6
for col:= 1 to M.Columns do BEGIN
for row:= 1 to M.Rows do write(M[row, col]:5:1);
writeln;
END;
M.Free;
END.
Базы данных
Создание
Создание 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' или любой другой удобный каталог.
ПРИМЕЧАНИЕ:
При любом изменении структуры таблицы Вам придется удалять кэш вручную. Имя файла, в котором хранится кэш, Вы можете узнать, посмотрев в любом текстовом редакторе файл SCache.INI.
Поиск
Поиск величины при вводе
Каким способом можно производить поиск подходящих величин в момент ввода? Табличный курсор (визуально) должен перемещаться к наиболее подходящему значению при добавлении пользователем новых символов водимой величины.
Первоначально код писался под 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 генерится ОЧЕНЬ часто и может быть необязательным и занимать большое количество времени, например, у вас есть таблица с неким вычисляемым полем, и при каждом редактировании таблицы вызывается следующий код: