George Judkin дата публикации 30-10-2011 16:12 Потребовалось мне в одном проекте строить в run-time формы на основании информации из базы данных. База данных, обработка по одной записи, да еще и создание новых компонент — очень большая вероятность медленной работы. Значит, согласно правилам хорошего тона, требуется какая-то индикация для отображения процесса. Посмотрел имеющиеся материалы, но, в основном, разработчики предлагают какие-то варианты внешнего индикатора, который крутится в отдельном потоке, развлекая пользователя, в то время как сама программа втихаря что-то делает. А мне в голову пришла другая идея.
Но сначала немного пояснений. У меня ситуация была такова, что не требовалось обеспечивать работоспособность основной программы, пока идет загрузка данных. Более того, основная программа вообще не должна позволять что-либо делать, пока не закончится загрузка (а то пользователь чего-то такого понажимает, а мне потом заниматься синхронизацией того, что он понажимал, с процессом загрузки). Так и пришла в голову мысль сделать модальное окно, которое бы само и данные грузило, и прогресс отображало (да еще бы и не давало пользователю наделать глупостей).
Программирования и без того было много, так что решил сделать как можно проще. Набросал быстренько форму, примерно вот таким образом:
То есть, TLabel для текста, TProgressBar для индикации процесса и TButton для отмены загрузки, если пользователь устанет ждать. Расставил необходимые свойства (в частности, TButton.ModalResult выставил в mrCancel, чтобы не писать обработчик нажатия кнопки). Пора переходить к реализации собственно загрузчика, а... А где его реализовывать-то? ShowModal — штука подлая: получит управление и не отдаст, пока форму не закроешь. Значит нужно искать какое-то событие. Вариант с вставкой загрузки в OnCreate получил отлуп сразу: даже пробовать не хочу, что получится, если пытаться использовать форму, которая еще до конца не создана. Остаются варианты типа OnActivate или OnShow. Более предпочтительным показался OnActivate, после чего был написан примерно такой код:
procedure TfrmLoader.AMDoLoad(var Msg : TMessage);
var
i : Integer;
begin
Progress.Max:=100;
for i:=1 to Progress.Max do
begin
Sleep(50);
Progress.StepIt;
Application.ProcessMessages;
if ModalResult <> mrNone then Exit;
end;
ModalResult:=mrOk;
end;
|
|
Для упрощения я выбросил конкретику по определению количества шагов и обработке на каждом шаге.
Все получилось хорошо. Все работало, грузилось и отображалось. Но не работала отмена. В общем, и не удивительно, что она не работала. Если заглянуть в модуль Forms.pas и посмотреть на реализацию метода TCustomForm.ShowModal, то сразу видно, что сначала посылается сообщение CM_ACTIVATE, а только после того, как оно будет обработано, будет установлено нулевое значение ModalResult и начнется цикл петли сообщений. И OnShow здесь не помощник, так как показ формы происходит еще раньше.
Но у нас имеется замечательный механизм — сообщения. Надо только знать, чем отличается SendMessage от PostMessage и не перепутать. От попытки использовать не по назначению какое-либо из уже существующих сообщений я отказался. Мы имеем право создавать и использовать свои собственные сообщения уровня приложения, вот и будем этим пользоваться. В итоге код получился примерно такой:
const
AM_DOLOAD = WM_USER+1;
type
TfrmLoader = class(TForm)
fldText: TLabel;
Progress: TProgressBar;
btnCancel: TButton;
procedure FormActivate(Sender : TObject);
private
FLoadStarted : Boolean;
procedure AMDoLoad(var Msg : TMessage); message AM_DOLOAD;
end;
implementation
procedure TfrmLoader.AMDoLoad(var Msg : TMessage);
var
i : Integer;
begin
Progress.Max:=100;
for i:=1 to Progress.Max do
begin
Sleep(50);
Progress.StepIt;
Application.ProcessMessages;
if ModalResult <> mrNone then Exit;
end;
ModalResult:=mrOk;
end;
procedure TfrmLoader.FormActivate(Sender : TObject);
begin
if FLoadStarted then Exit;
FLoadStarted:=true;
PostMessage(Handle,AM_DOLOAD,0,0);
end;
|
|
Собственно, все. Содержательная часть работы перекочевала в метод AMDoLoad. После вызова ShowModal для формы в обработчике OnActivate мы сначала проверяем, не запускался ли уже раньше цикл загрузки (вот такой я маньяк-перестраховщик), если нет, то посылаем сами себе сообщение AM_DOLOAD и сразу выходим, не дожидаясь пока обработается сообщение. А сообщение и не будет пока обрабатываться. Сначала отработает активация, потом начнется цикл петли сообщений, наше сообщение будет извлечено из очереди, и начнется загрузка. Если не нажимать кнопку, то после выполнения загрузки ShowModal закончит работу, вернув нам значение mrOK. Если нажать кнопку, то — mrCancel.
Задача, вроде как, решена. Но есть в этом что-то... не то. Ведь вещь общезначимая, так что хотелось бы сделать так, чтобы потом легко можно было использовать и в других проектах. А заодно и сказать несколько слов о том, как надо делать, а как не надо.
Есть сейчас такое поветрие использовать оставшееся до завершения процесса время в качестве параметра для отображения хода выполнения процесса. Не знаю, кому как, а меня это сильно раздражает. И ожидание полтора часа, пока загрузится файл, до завершения загрузки которого осталось 3 секунды. И страшное сообщение о том, что копирование файла (сравнительного небольшого) займет 20 минут (а на деле — две). В общем, если нам операционная система не гарантирует точное время выполнения процесса, то использование в данном случае времени — это просто обман пользователя. Уж лучше тогда положить анимированную картинку и вообще ничего не обещать.
Поэтому я обычно стараюсь использовать конкретные перечислимые параметры (количество обрабатываемых файлов или записей в БД, количество загружаемых модулей и т.п.). Тут, правда, придется дополнительно затратить время на подсчет количества элементов, которые нужно обработать. Но, по-моему, это незначительные затраты, которые с лихвой окупаются улучшением юзабилити программы. Поэтому предлагается вот такой модуль.
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, ComCtrls;
const
AM_STARTLOADING = WM_USER+1;
type
TfrmLoader = class(TForm)
fldText : TLabel;
ProgressBar : TProgressBar;
btnCancel : TBitBtn;
procedure FormActivate(Sender : TObject);
private
FUsing : Boolean;
procedure AMStartLoading(var Msg : TMessage); message AM_STARTLOADING;
protected
function CalcStepCount : Integer; virtual;
function ProceedStep(StepNum : Integer) : Boolean; virtual;
public
function LoadData : Boolean;
end;
implementation
const
msgInfIsPreparing = 'Идет подготовка данных...';
msgInfIsLoading = 'Идет загрузка данных...';
procedure TfrmLoader.AMStartLoading(var Msg : TMessage);
var
i : Integer;
begin
fldText.Caption:=msgInfIsPreparing;
Application.ProcessMessages;
ProgressBar.Max:=CalcStepCount;
fldText.Caption:=msgInfIsLoading;
Application.ProcessMessages;
for i:=1 to ProgressBar.Max do
begin
if not ProceedStep(i) then ModalResult:=mrCancel;
if ModalResult <> mrNone then Exit;
ProgressBar.StepIt;
Application.ProcessMessages;
end;
ModalResult:=mrOK;
end;
function TfrmLoader.CalcStepCount : Integer;
begin
Result:=20;
end;
function TfrmLoader.ProceedStep(StepNum : Integer) : Boolean;
begin
Result:=true;
end;
function TfrmLoader.LoadData : Boolean;
begin
Result:=(ShowModal = mrOK);
end;
procedure TfrmLoader.FormActivate(Sender : TObject);
begin
if FUsing then Exit;
FUsing:=true;
PostMessage(Handle,AM_STARTLOADING,0,0);
end;
end.
|
|
Здесь я явно выделил два метода — CalcStepCount и ProceedStep — в которых нужно вычислить количество шагов загрузки, и выполнить каждый шаг. Методы виртуальные, чтобы при необходимости можно было изменить реализацию в классах-наследниках. Именно реализацию этих двух методов необходимо будет прописать для данного класса. Все остальное — обще-универсальное. Можно доработать, а можно и так оставить.
Если реализация protected-методов, задающих специфику формы, прописана, то использование очень простое: создаем экземпляр формы и вызываем public-метод LoadData. По возвращаемому значению судим об успешности завершения процесса. Все. Потом только уничтожить форму надо не забыть.
У меня эта форма вообще прописалась в репозитории. Как мне кажется, она ничуть не хуже, чем уже имеющиеся там About box или Dual list box.
Как водится, к материалу прилагается демонстрационная программа. Как водится, простенькая и не представляющая особой ценности. Указываем файл, а программа для него строит частотную характеристику — гистограмму, в которой каждому из возможных значений байта поставлен в соответствие один столбец таким образом, что высота столбца пропорциональна тому, сколько раз встречается этот байт в файле.
Здесь надо сказать, наверное, только одно. Для обработки файла используется буфер размером в полмегабайта. Количество шагов, таким образом, будет определяться тем, на сколько частей разобьется интересующий нас файл, если размер каждой части равен полмегабайта. Отсюда и соображения по тому, на каких файлах гонять программу. Лучше всего, когда количество шагов порядка сотни. Соответственно, используйте файлы размером порядка 50 мегабайт. Если файл маленький, то вы просто ничего не успеете увидеть, а если большой, то придется долго ждать завершения загрузки.
К материалу прилагаются файлы:
[Модальные формы (режим ShowModal)] [Обработка длительных процессов (отображение/реакция и т.п.)]
Обсуждение материала [ 28-04-2012 10:58 ] 15 сообщений |