Язык программирования самого высокого уровня содержит всего несколько команд для управления программистами
26 марта 2010
24 марта 2010
Delphi 2010: TDataSet.FreeBookmark – рудимент
Часто бывает полезно отметить текущее положение курсора в DataSet'е так, чтобы позже можно было быстро возвратиться к этому месту. Delphi обеспечивает эту функциональную возможность с помощью закладок (Bookmark), для работы с которыми используются процедуры:
Var
q: TSDQuery;
bm: TBookmark;
begin
Try
bm := q.GetBookmark; // делаем закладку
// обрабатываем ds
Finally
If bm <> nil then
begin
If q.BookmarkValid(bm) then
q.GotoBookmark(bm);
q.FreeBookmark(bm);
end;
End;
end;
В Delphi 7 код FreeBookmark выглядит так:
procedure TDataSet.FreeBookmark(Bookmark: TBookmark);
begin
FreeMem(Bookmark);
end;
В Delphi 2010 он – просто заглушка:
procedure TDataSet.FreeBookmark(Bookmark: TBookmark);
begin
// No longer need to free bookmark since it's a TBytes now.
end;
Т.е. FreeBookmark в Delphi 2010 – это уже пережиток прошлого и вызывать его больше не нужно. Таким образом, исходный код становится проще:
Var
ds: TSDQuery;
bm: TBookmark;
begin
Try
bm := q.GetBookmark; // делаем закладку
// обрабатываем ds
Finally
If (bm <> nil) and q.BookmarkValid(bm) then
q.GotoBookmark(bm);
End;
end;
P.S. А вот справочную систему Delphi 2010 подправить, как всегда, забыли. В ней есть раздел "DB.TDataSet.FreeBookmark" с описанием процедуры и рекомендацией ее использования. А раздел "Marking and Returning to Records" содержит строку "FreeBookmark frees the memory allocated for a specified bookmark when you no longer need it. You should also call DB.TDataSet.FreeBookmark before reusing an existing bookmark." и пример, где используется FreeBookmark :)
- GetBookmark – устанавливает закладку на текущую запись;
- BookmarkValid – проверяет, существует ли запись, на которую ссылается закладка;
- GotoBookmark – позиционирует курсор на запись, на которую ссылается закладка;
- FreeBookmark – освобождает системные ресурсы, используемые методом GetBookmark.
Var
q: TSDQuery;
bm: TBookmark;
begin
Try
bm := q.GetBookmark; // делаем закладку
// обрабатываем ds
Finally
If bm <> nil then
begin
If q.BookmarkValid(bm) then
q.GotoBookmark(bm);
q.FreeBookmark(bm);
end;
End;
end;
В Delphi 7 код FreeBookmark выглядит так:
procedure TDataSet.FreeBookmark(Bookmark: TBookmark);
begin
FreeMem(Bookmark);
end;
В Delphi 2010 он – просто заглушка:
procedure TDataSet.FreeBookmark(Bookmark: TBookmark);
begin
// No longer need to free bookmark since it's a TBytes now.
end;
Т.е. FreeBookmark в Delphi 2010 – это уже пережиток прошлого и вызывать его больше не нужно. Таким образом, исходный код становится проще:
Var
ds: TSDQuery;
bm: TBookmark;
begin
Try
bm := q.GetBookmark; // делаем закладку
// обрабатываем ds
Finally
If (bm <> nil) and q.BookmarkValid(bm) then
q.GotoBookmark(bm);
End;
end;
P.S. А вот справочную систему Delphi 2010 подправить, как всегда, забыли. В ней есть раздел "DB.TDataSet.FreeBookmark" с описанием процедуры и рекомендацией ее использования. А раздел "Marking and Returning to Records" содержит строку "FreeBookmark frees the memory allocated for a specified bookmark when you no longer need it. You should also call DB.TDataSet.FreeBookmark before reusing an existing bookmark." и пример, где используется FreeBookmark :)
21 марта 2010
Разработчики Delphi собрали 26 000$ для помощи Гаити
Anders Ohlsson, сотрудник Embarcadero Technologies (бывший сотрудник компании Borland), организовал на eBay благотворительный аукцион для помощи населению пострадавшего от землетрясения Гаити.
На аукцион были выставлены старые вещи, связанные с компанией Borland. За 2 025$ был продан Compaq II – компьютер, на котором Anders Hejlsberg разрабатывал Turbo Pascal. За 99$ продали наклейку на бампер "Delphi Developers Do It Faster". С молотка ушли подписанные авторами книги "Delphi 2 Developer's Guide" (Steve Teixeira) и "Delphi Component Design" (Danny Thorpe), старые релизы продуктов в оригинальных упаковках, фирменные футболки компании, эксклюзивная спортивная куртка с большой буквой "B" и прочее. Обед с David Intersimone ("David I", вице-президент по связям с разработчиками и главный евангелист Embarcadero Technologies) оценили в 480$.
Всего поклонниками компании Borland со всех континентов было куплено 177 предметов общей стоимостью 26 000$. Некто то, из Австрии, потратил на этом аукционе почти 4 200$!
Собранные деньги будут переданы фонду "Clinton Bush Haiti Fund".
На аукцион были выставлены старые вещи, связанные с компанией Borland. За 2 025$ был продан Compaq II – компьютер, на котором Anders Hejlsberg разрабатывал Turbo Pascal. За 99$ продали наклейку на бампер "Delphi Developers Do It Faster". С молотка ушли подписанные авторами книги "Delphi 2 Developer's Guide" (Steve Teixeira) и "Delphi Component Design" (Danny Thorpe), старые релизы продуктов в оригинальных упаковках, фирменные футболки компании, эксклюзивная спортивная куртка с большой буквой "B" и прочее. Обед с David Intersimone ("David I", вице-президент по связям с разработчиками и главный евангелист Embarcadero Technologies) оценили в 480$.
Всего поклонниками компании Borland со всех континентов было куплено 177 предметов общей стоимостью 26 000$. Некто то, из Австрии, потратил на этом аукционе почти 4 200$!
Собранные деньги будут переданы фонду "Clinton Bush Haiti Fund".
По информации Santa Cruz Sentinel
16 марта 2010
Delphi 2010: TValue - "тормоз"!
Перевод. Оригинал "TValue is very slow" (© TURBU Tech) дополнен моими тестами и комментариями.
Справочная система Delphi 2010 описывает тип TValue, используемый модулем RTTI для хранения значений произвольных типов, как "облегченная версия типа Variant". Увидев это, я задался вопросом, насколько он легковеснее? Как быстро работает TValue?
К счастью, среди известных мне новых возможностей Delphi 2010 – модуль диагностики (Diagnostics), который предоставляет нам объект TStopwatch – простой таймер позволяющий засечь время выполнения операций и тем самым облегчить написание простого теста скорости.
Я ожидал, что скорость работы TValue будет сравнима со скоростью Variant, или возможно немного больше. Для проверки, я написал следующую программку:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, rtti, diagnostics;
const
HUNDRED_MILLION = 100000000;
procedure tryTValue;
var
i: integer;
j: TValue;
value: integer;
begin
for I := 1 to HUNDRED_MILLION do
begin
j := i;
value := j.AsInteger;
end;
end;
procedure tryVariants;
var
i: integer;
j: variant;
value: integer;
begin
for I := 1 to HUNDRED_MILLION do
begin
j := i;
value := j;
end;
end;
var
stopwatch: TStopWatch;
begin
try
stopwatch := TStopWatch.StartNew;
tryVariants;
stopwatch.Stop;
writeln('Variants: ', stopwatch.ElapsedMilliseconds);
stopwatch := TStopWatch.StartNew;
tryTValue;
stopwatch.Stop;
writeln('TValues: ', stopwatch.ElapsedMilliseconds);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
end.
Конечно, этот тест - не исчерпывающая проверка возможностей TValue, но результаты поучительны. Когда я запустил его на своем рабочем компьютере (высокопроизводительный ноутбук Alienware), тест Variant выполнился почти мгновенно, а тест TValue выполнялся так долго, что я решил, что он завис и остановил его.
Затем я запустил тест снова и получил следующие результаты (в миллисекундах):
От меня.
Вообще то 31131 поделить на 717 будет равно 43.41841004 ;)
Что бы проверить результат, я запустил несколько тестов на своей домашней "dev machine".
Тест #1. Запустил исходный тест и получил такую же разницу скорости - в 43.17657992 раза:
Тест #4. В процедуре tryTValue я заменил "value := j.AsInteger" на "value := j.AsOrdinal"
Тест #5. AsOrdinal возвращает значение типа Int64, поэтому в процедуре tryTValue я заменил "value := j.AsInteger" на "value := j.AsInt64" и получил падение скорости в 54,25981308 раза!!!
Напоследок, я проверил с помощью функции SizeOf число байт, которые занимали переменные: переменная типа Variant занимала – 24 байта, а TValue – всего 16. Может в этом проявляется "облегченность" типа TValue? Тогда, храните числа в integer – они будут занимать 4 байта ;)
Справочная система Delphi 2010 описывает тип TValue, используемый модулем RTTI для хранения значений произвольных типов, как "облегченная версия типа Variant". Увидев это, я задался вопросом, насколько он легковеснее? Как быстро работает TValue?
К счастью, среди известных мне новых возможностей Delphi 2010 – модуль диагностики (Diagnostics), который предоставляет нам объект TStopwatch – простой таймер позволяющий засечь время выполнения операций и тем самым облегчить написание простого теста скорости.
Я ожидал, что скорость работы TValue будет сравнима со скоростью Variant, или возможно немного больше. Для проверки, я написал следующую программку:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, rtti, diagnostics;
const
HUNDRED_MILLION = 100000000;
procedure tryTValue;
var
i: integer;
j: TValue;
value: integer;
begin
for I := 1 to HUNDRED_MILLION do
begin
j := i;
value := j.AsInteger;
end;
end;
procedure tryVariants;
var
i: integer;
j: variant;
value: integer;
begin
for I := 1 to HUNDRED_MILLION do
begin
j := i;
value := j;
end;
end;
var
stopwatch: TStopWatch;
begin
try
stopwatch := TStopWatch.StartNew;
tryVariants;
stopwatch.Stop;
writeln('Variants: ', stopwatch.ElapsedMilliseconds);
stopwatch := TStopWatch.StartNew;
tryTValue;
stopwatch.Stop;
writeln('TValues: ', stopwatch.ElapsedMilliseconds);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
end.
Конечно, этот тест - не исчерпывающая проверка возможностей TValue, но результаты поучительны. Когда я запустил его на своем рабочем компьютере (высокопроизводительный ноутбук Alienware), тест Variant выполнился почти мгновенно, а тест TValue выполнялся так долго, что я решил, что он завис и остановил его.
Затем я запустил тест снова и получил следующие результаты (в миллисекундах):
Variants: 717По крайней мере, для этой конкретной операции, TValue в 43.52167832167832 раза медленнее, чем Variant!
TValues: 31131
От меня.
Вообще то 31131 поделить на 717 будет равно 43.41841004 ;)
Что бы проверить результат, я запустил несколько тестов на своей домашней "dev machine".
Тест #1. Запустил исходный тест и получил такую же разницу скорости - в 43.17657992 раза:
Variants: 538Тест #2. Переставил местами вызов tryTValue и tryVariants и снова получил 43.376404494:
TValues: 23229.
TValues: 23163Тест #3. Закомментировав обратное присвоение TValue и Variant целой переменной ("value := j.AsInteger" и "value := j") я получил для TValue более "веселый" результат:
Variants: 534.
Variants: 535Присвоение целого значения TValue медленнее присвоения целого значения Variant всего в 9.65981308 раза. А значит основное падение скорости вызвано AsInteger.
TValues: 5168
Тест #4. В процедуре tryTValue я заменил "value := j.AsInteger" на "value := j.AsOrdinal"
Variants: 536В результате общее падение скорости всего в 10.93656716 раза!
TValues: 5862
Тест #5. AsOrdinal возвращает значение типа Int64, поэтому в процедуре tryTValue я заменил "value := j.AsInteger" на "value := j.AsInt64" и получил падение скорости в 54,25981308 раза!!!
Variants: 535Вывод: "value := j.AsOrdinal" у TValue работает почти так же быстро, как и "value := j" для Variant. А методы AsInteger и AsInt64 – лучше не использовать. Но все равно, главный вывод: TValue – "тормоз"!
TValues: 29029
Напоследок, я проверил с помощью функции SizeOf число байт, которые занимали переменные: переменная типа Variant занимала – 24 байта, а TValue – всего 16. Может в этом проявляется "облегченность" типа TValue? Тогда, храните числа в integer – они будут занимать 4 байта ;)
15 марта 2010
Использование полей и закладок при работе с MS Word из Delphi
В предыдущей заметке "Поиск и замена текста в документе MS Word из Delphi" я рассказывал, как дорабатывал старый модуль, который генерирует клиентам компании письма в формате MS Word с помощью поиска и замены текста. Сдав модуль заказчикам, я в свободное от работы время, переделал его. Вместо поиска и замены использовал поля с переменными (DocVariable).
В шаблон письма с помощью макроса добавил переменные
Sub AddFields()
ThisDocument.Variables.Add "FIO", "FIO"
ThisDocument.Variables.Add "ADDRESS", "ADDRESS"
...
End Sub
и расставил поля по тексту шаблона. В макросе у метода Add первый параметр – название переменной, а второй – ее значение. Я специально сделал их одинаковыми, чтобы пользователям было проще и нагляднее редактировать шаблоны.
Затем внес изменения в методы модуля работающие с MS Word. Если опустить все детали и различную логику, то упрощенно работа с MS Word выглядит так:
В шаблон письма с помощью макроса добавил переменные
Sub AddFields()
ThisDocument.Variables.Add "FIO", "FIO"
ThisDocument.Variables.Add "ADDRESS", "ADDRESS"
...
End Sub
и расставил поля по тексту шаблона. В макросе у метода Add первый параметр – название переменной, а второй – ее значение. Я специально сделал их одинаковыми, чтобы пользователям было проще и нагляднее редактировать шаблоны.
Затем внес изменения в методы модуля работающие с MS Word. Если опустить все детали и различную логику, то упрощенно работа с MS Word выглядит так:
Var
wa: WordApplication;
ovDotName, ovFileName: OleVariant;
i: Integer;
q: TSDQuery;
...
begin
...
wa := CreateComObject(CLASS_WordApplication) as _Application;
ovDotName := 'какой то шаблон.dot';
wa.Documents.Add(ovDotName, EmptyParam, EmptyParam, EmptyParam);
For i := 0 to q.FieldCount-1 do
MSWordSetVariable(q.Fields[i].FieldName, q.Fields[i].AsString);
...
ovFileName := 'письмо любимому клиенту.doc';
wa.ActiveDocument.SaveAs(ovFileName, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
...
wa: WordApplication;
ovDotName, ovFileName: OleVariant;
i: Integer;
q: TSDQuery;
...
begin
...
wa := CreateComObject(CLASS_WordApplication) as _Application;
ovDotName := 'какой то шаблон.dot';
wa.Documents.Add(ovDotName, EmptyParam, EmptyParam, EmptyParam);
For i := 0 to q.FieldCount-1 do
MSWordSetVariable(q.Fields[i].FieldName, q.Fields[i].AsString);
...
ovFileName := 'письмо любимому клиенту.doc';
wa.ActiveDocument.SaveAs(ovFileName, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam);
...
Где MSWordSetVariable – процедура, которая присваивает значение переменной в документе MS Word.
procedure MSWordSetVariable(ovVariable: OleVariant; const sValue: String);
begin
If sValue = ''
then wa.ActiveDocument.Variables.Item(ovVariable).Value := ' '
else wa.ActiveDocument.Variables.Item(ovVariable).Value := sValue
end;
begin
If sValue = ''
then wa.ActiveDocument.Variables.Item(ovVariable).Value := ' '
else wa.ActiveDocument.Variables.Item(ovVariable).Value := sValue
end;
Если переменной MS Word присвоить пустую строчку, то в поле выводится текст 'Ошибка! Переменная документа не указана.', поэтому вместо пустого значения я присваиваю ей пробел (так же делает и сам MS Word). Это связано со странной работой MS Word с коллекцией переменных. Если переменной присвоить пустую строку, то количество переменных в коллекции (Variables.Count) уменьшается на единицу, а попытка удалить переменную (Variables.Item(ovVariable).Delete) дает ошибку 'Объект был удален', т.е. присвоение пустой строки равносильно удалению. При этом после удаления переменной из коллекции переменных, присвоение ей непустого значения выполняется успешно и количество переменных в коллекции увеличивается на единицу, т.е. присвоение непустого значения равносильно вызову метода добавления переменной в коллекцию переменных.
Продемонстрирую вышесказанное примером кода, по которому видно как изменяется количество переменных (iCount):
Продемонстрирую вышесказанное примером кода, по которому видно как изменяется количество переменных (iCount):
iCount := wa.ActiveDocument.Variables.Count; // iCount = 3
wa.ActiveDocument.Variables.Item(ovVariable).Value := '';
iCount := wa.ActiveDocument.Variables.Count; // iCount = 2
wa.ActiveDocument.Variables.Item(ovVariable).Value := 'не пусто';
iCount := wa.ActiveDocument.Variables.Count; // iCount = 3
wa.ActiveDocument.Variables.Item(ovVariable).Delete;
iCount := wa.ActiveDocument.Variables.Count; // iCount = 2
wa.ActiveDocument.Variables.Item(ovVariable).Value := 'не пусто';
iCount := wa.ActiveDocument.Variables.Count; // iCount = 3
wa.ActiveDocument.Variables.Item(ovVariable).Value := '';
iCount := wa.ActiveDocument.Variables.Count; // iCount = 2
wa.ActiveDocument.Variables.Item(ovVariable).Delete; // ошибка 'Объект был удален'
wa.ActiveDocument.Variables.Item(ovVariable).Value := '';
iCount := wa.ActiveDocument.Variables.Count; // iCount = 2
wa.ActiveDocument.Variables.Item(ovVariable).Value := 'не пусто';
iCount := wa.ActiveDocument.Variables.Count; // iCount = 3
wa.ActiveDocument.Variables.Item(ovVariable).Delete;
iCount := wa.ActiveDocument.Variables.Count; // iCount = 2
wa.ActiveDocument.Variables.Item(ovVariable).Value := 'не пусто';
iCount := wa.ActiveDocument.Variables.Count; // iCount = 3
wa.ActiveDocument.Variables.Item(ovVariable).Value := '';
iCount := wa.ActiveDocument.Variables.Count; // iCount = 2
wa.ActiveDocument.Variables.Item(ovVariable).Delete; // ошибка 'Объект был удален'
Вот такая логика у индусов, писавших этот кусок MS Word.
После присвоения значений всем переменным, осталось только дать команду полям обновиться новыми значениями переменных. Для этого в VBA у коллекции объектов полей (Fields) есть метод Update:
wa.ActiveDocument.Fields.Update
А для того, чтобы избежать дальнейшего обновления полей, избавимся от их связи с переменными:
wa.ActiveDocument.Fields.Unlink
У каждого структурного элемента документа (заметки, колонтитула, сноски и т.д.) своя коллекция объектов полей, поэтому, если документ, как у меня, состоит из разных структурных элементов, то методы Update и Unlink необходимо вызвать для каждого из этих элементов. Для этого перебираем все элементы коллекции StoryRanges.
После присвоения значений всем переменным, осталось только дать команду полям обновиться новыми значениями переменных. Для этого в VBA у коллекции объектов полей (Fields) есть метод Update:
wa.ActiveDocument.Fields.Update
А для того, чтобы избежать дальнейшего обновления полей, избавимся от их связи с переменными:
wa.ActiveDocument.Fields.Unlink
У каждого структурного элемента документа (заметки, колонтитула, сноски и т.д.) своя коллекция объектов полей, поэтому, если документ, как у меня, состоит из разных структурных элементов, то методы Update и Unlink необходимо вызвать для каждого из этих элементов. Для этого перебираем все элементы коллекции StoryRanges.
procedure MSWordUpdateStoryRanges;
Var
StoryRanges: Word2000.StoryRanges;
StoryRange: Word2000.Range;
iStoryIndex: integer;
Begin
StoryRanges := wa.ActiveDocument.StoryRanges;
For iStoryIndex := wdMainTextStory to wdFirstPageFooterStory do
Try
StoryRange := StoryRanges.Item(iStoryIndex);
If StoryRange <> nil then
begin
StoryRange.Fields.Update;
StoryRange.Fields.Unlink;
While (StoryRange.NextStoryRange <> nil) do
begin
StoryRange := StoryRange.NextStoryRange;
StoryRange.Fields.Update;
StoryRange.Fields.Unlink;
end;
end;
Except
StoryRange := nil;
End;
End;
Var
StoryRanges: Word2000.StoryRanges;
StoryRange: Word2000.Range;
iStoryIndex: integer;
Begin
StoryRanges := wa.ActiveDocument.StoryRanges;
For iStoryIndex := wdMainTextStory to wdFirstPageFooterStory do
Try
StoryRange := StoryRanges.Item(iStoryIndex);
If StoryRange <> nil then
begin
StoryRange.Fields.Update;
StoryRange.Fields.Unlink;
While (StoryRange.NextStoryRange <> nil) do
begin
StoryRange := StoryRange.NextStoryRange;
StoryRange.Fields.Update;
StoryRange.Fields.Unlink;
end;
end;
Except
StoryRange := nil;
End;
End;
Т.к. в моем шаблоне есть только основной текст и одна заметка, то вместо процедуры, которая перебирает все элементы коллекции StoryRanges, я сделал процедуру, которая работает только с одним ее элементом:
procedure MSWordUpdateStoryRange(const iStoryIndex: integer);
Var
StoryRange: Word2000.Range;
begin
Try
StoryRange := wa.ActiveDocument.StoryRanges.Item(iStoryIndex);
If StoryRange <> nil then
begin
StoryRange.Fields.Update;
StoryRange.Fields.Unlink;
end;
Except
End;
end;
Var
StoryRange: Word2000.Range;
begin
Try
StoryRange := wa.ActiveDocument.StoryRanges.Item(iStoryIndex);
If StoryRange <> nil then
begin
StoryRange.Fields.Update;
StoryRange.Fields.Unlink;
end;
Except
End;
end;
И вызываю ее перед сохранением документа.
MSWordUpdateStoryRange(wdMainTextStory);
MSWordUpdateStoryRange(wdTextFrameStory);
Все бы ничего, но часть текста передаваемого из программы в MS Word необходимо было выводить жирным шрифтом. А это через переменные не сделать :( В подобном случае "поиск и замену текста" можно заменить на "переход к закладке и вывод текста". Например, в шаблон вставляем закладку с именем 'писать текст сюда', а в программе пишем:
MSWordUpdateStoryRange(wdMainTextStory);
MSWordUpdateStoryRange(wdTextFrameStory);
Все бы ничего, но часть текста передаваемого из программы в MS Word необходимо было выводить жирным шрифтом. А это через переменные не сделать :( В подобном случае "поиск и замену текста" можно заменить на "переход к закладке и вывод текста". Например, в шаблон вставляем закладку с именем 'писать текст сюда', а в программе пишем:
Var
ovBookmarkName, ovWhat: OLEVariant;
begin
ovWhat := wdGoToBookmark;
ovBookmarkName := 'писать текст сюда';
Try
If wa.Selection.GoTo_(ovWhat, EmptyParam, EmptyParam, ovBookmarkName) <> nil then
begin
wa.Selection.TypeText('просто текст ');
wa.Selection.Font.Bold := 1;
wa.Selection.TypeText('жирный текст');
wa.Selection.Font.Bold := 0;
wa.Selection.TypeText(' просто текст');
end;
Except
End;
ovBookmarkName, ovWhat: OLEVariant;
begin
ovWhat := wdGoToBookmark;
ovBookmarkName := 'писать текст сюда';
Try
If wa.Selection.GoTo_(ovWhat, EmptyParam, EmptyParam, ovBookmarkName) <> nil then
begin
wa.Selection.TypeText('просто текст ');
wa.Selection.Font.Bold := 1;
wa.Selection.TypeText('жирный текст');
wa.Selection.Font.Bold := 0;
wa.Selection.TypeText(' просто текст');
end;
Except
End;
Вот и все :)
Раннее связывание и использование полей/закладок дало существенный рост скорости генерации писем. Если на 500-х различных документах этого было почти не заметно, то при генерации 15 000 документов, прирост скорости составил 30% (специально проверил несколько раз на одних и тех же исходных данных).
P.S. При работе с ранним связыванием мне не нравится только одно – многие параметры в методах объявлены, как VAR (даже индекс элемента коллекции!), поэтому приходится заводить для них специальную переменную типа OleVariant.
Раннее связывание и использование полей/закладок дало существенный рост скорости генерации писем. Если на 500-х различных документах этого было почти не заметно, то при генерации 15 000 документов, прирост скорости составил 30% (специально проверил несколько раз на одних и тех же исходных данных).
P.S. При работе с ранним связыванием мне не нравится только одно – многие параметры в методах объявлены, как VAR (даже индекс элемента коллекции!), поэтому приходится заводить для них специальную переменную типа OleVariant.
09 марта 2010
Поиск и замена текста в документе MS Word из Delphi
Попросили меня доработать старый модуль, который генерирует клиентам компании письма в формате MS Word. Пользователи создают шаблоны, в которых расставляют названия полей, заключенные в служебные символы (например, #CONTRACT_NUM#, #FIO#, #ADDRESS#...). Программа по заданным критериям выбирает информацию о клиентах из базы и генерирует письма, находя в тексте шаблонов названия полей и заменяя их фактическими значениями.
Если опустить все детали и различную логику, то упрощенно это выглядит так:
Var
MSWord: OleVariant;
i: Integer;
q: TSDQuery;
...
begin
...
MSWord := CreateOleObject('Word.Application');
MSWord.Documents.Add('какой то шаблон.dot');
For i := 0 to q.FieldCount-1 do
MSWord.Selection.Find.Execute(FindText := '#' + q.Fields[i].FieldName + '#', ReplaceWith := q.Fields[i].AsString);
...
MSWord.ActiveDocument.SaveAs('письмо любимому клиенту.doc');
...
Первое, с чем я столкнулся, это то, что длина значения параметра ReplaceWith не должна превышать 255 символов. Но это я обошел легко, заменив "поиск и замену текста" на "поиск и вывод текста":
If MSWord.Selection.Find.Execute(FindText := '#' + q.Fields[i].FieldName + '#') then
MSWord.Selection.TypeText(q.Fields[i].AsString);
Вторая задачка оказалась сложнее. Внизу листа в фиксированное место необходимо было вывести фамилию и адрес получателя. Сначала я думал поместить его в нижний колонтитул, но оказалось, что письмо может быть на двух листах. Тогда ничего не оставалось, как использовать объект "заметка". Вставил "заметку". Красота! Документ генерируется, текст сдвигается, заметка остается на месте... Но радость была недолгой, т.к. поля #FIO# и #ADDRESS#, помещенные в заметку, так и остались незамененными :(
Оказалось, что MSWord.Selection.Find.Execute ищет текст только в основной части документа, а в документе, состоящем из разных структурных элементов (заметок, колонтитулов, сносок и т.д.), поиск необходимо производить отдельно в каждом из этих элементов. Все эти структурные элементы документа являются элементами коллекции StoryRanges. Т.к. дело было к ночи, а модуль должен был быть готов к утру, я не стал разбираться, как работать со StoryRanges через OLE из Delphi, и просто добавил в тестовый шаблон письма макрос на VBA, в котором перебираются все структурные элементы активного документа, в которых ведется поиск:
Sub ReplaceText(sFindText As String, sReplaceText As String)
Dim rngStory As Range
For Each rngStory In ActiveDocument.StoryRanges
With rngStory.Find
.Text = sFindText
.Replacement.Text = sReplaceText
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next rngStory
End Sub
А в программе я только написал вызов макроса:
MSWord.Application.Run('ReplaceText', '#FIO#', 'Иванов Иван Иванович');
Работает как часы :)
P.S. После окончания генерации документа, если MS Word вам больше не нужен, то не забывайте закрывать его и высвобождать память
MSWord.Quit;
MSWord := UnAssigned;
P.P.S. Для подобной задачи генерации писем в формат MS Word больше подходит не поиск и замена текста, использование полей с переменными (DocVariable) и закладок (Bookmark). А как это сделать, я расскажу в следующий раз.
Если опустить все детали и различную логику, то упрощенно это выглядит так:
Var
MSWord: OleVariant;
i: Integer;
q: TSDQuery;
...
begin
...
MSWord := CreateOleObject('Word.Application');
MSWord.Documents.Add('какой то шаблон.dot');
For i := 0 to q.FieldCount-1 do
MSWord.Selection.Find.Execute(FindText := '#' + q.Fields[i].FieldName + '#', ReplaceWith := q.Fields[i].AsString);
...
MSWord.ActiveDocument.SaveAs('письмо любимому клиенту.doc');
...
Первое, с чем я столкнулся, это то, что длина значения параметра ReplaceWith не должна превышать 255 символов. Но это я обошел легко, заменив "поиск и замену текста" на "поиск и вывод текста":
If MSWord.Selection.Find.Execute(FindText := '#' + q.Fields[i].FieldName + '#') then
MSWord.Selection.TypeText(q.Fields[i].AsString);
Вторая задачка оказалась сложнее. Внизу листа в фиксированное место необходимо было вывести фамилию и адрес получателя. Сначала я думал поместить его в нижний колонтитул, но оказалось, что письмо может быть на двух листах. Тогда ничего не оставалось, как использовать объект "заметка". Вставил "заметку". Красота! Документ генерируется, текст сдвигается, заметка остается на месте... Но радость была недолгой, т.к. поля #FIO# и #ADDRESS#, помещенные в заметку, так и остались незамененными :(
Оказалось, что MSWord.Selection.Find.Execute ищет текст только в основной части документа, а в документе, состоящем из разных структурных элементов (заметок, колонтитулов, сносок и т.д.), поиск необходимо производить отдельно в каждом из этих элементов. Все эти структурные элементы документа являются элементами коллекции StoryRanges. Т.к. дело было к ночи, а модуль должен был быть готов к утру, я не стал разбираться, как работать со StoryRanges через OLE из Delphi, и просто добавил в тестовый шаблон письма макрос на VBA, в котором перебираются все структурные элементы активного документа, в которых ведется поиск:
Sub ReplaceText(sFindText As String, sReplaceText As String)
Dim rngStory As Range
For Each rngStory In ActiveDocument.StoryRanges
With rngStory.Find
.Text = sFindText
.Replacement.Text = sReplaceText
.Wrap = wdFindContinue
.Execute Replace:=wdReplaceAll
End With
Next rngStory
End Sub
А в программе я только написал вызов макроса:
MSWord.Application.Run('ReplaceText', '#FIO#', 'Иванов Иван Иванович');
Работает как часы :)
P.S. После окончания генерации документа, если MS Word вам больше не нужен, то не забывайте закрывать его и высвобождать память
MSWord.Quit;
MSWord := UnAssigned;
P.P.S. Для подобной задачи генерации писем в формат MS Word больше подходит не поиск и замена текста, использование полей с переменными (DocVariable) и закладок (Bookmark). А как это сделать, я расскажу в следующий раз.
04 марта 2010
Delphi 2010: Сносим назойливый Code Formatter
В далекие школьные годы у нас в школе стоял компьютер ДВК-2М. По сравнению с общераспространенными БК - это было чудо техники, с нормальным монитором и даже с винчестером. На нем я писал свои первые программки. Однажды, когда я писал очередной "шедевр", в соседний кабинет вошла уборщица и включила свет. Этот щелчок включателя я запомнил надолго. При включении света, компьютер моргнул экраном и начал перезагружаться, унеся с собой больше часа моей работы. Это послужило для меня уроком. С тех пор, я регулярно сохраняюсь и, раз в несколько минут, мои руки автоматически жмут Ctrl+S.
Когда я начал писать свою первую программу на Delphi 2010, я случайно промахнулся и нажал Ctrl+D вместо Ctrl+S. Каково было мое изумление, когда я заметил, что код программы стал выглядеть совсем не так, как я привык его форматировать за свои 14 лет работы с Delphi.
Когда я начал писать свою первую программу на Delphi 2010, я случайно промахнулся и нажал Ctrl+D вместо Ctrl+S. Каково было мое изумление, когда я заметил, что код программы стал выглядеть совсем не так, как я привык его форматировать за свои 14 лет работы с Delphi.
Подписаться на:
Сообщения (Atom)