![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Окно - прогресс в отдельном потоке [D7, WinXP]
Дмитрий Белькевич © (2008-07-17 19:28:00)Нужно реализовать простую прогресс-форму с прогресс баром, небольшой картинкой и кнопкой прерывания процесса.
Нужно, что бы она работала в отдельном потоке.
То, что обычные формы работать в потоке не будут, знаю.
То что рекомендуют выносить код в поток знаю. Но не представляется возможным - прогресс применяется где-то в 25-30 местах и делать 30 разных потоков нецелесообразно.
Переделываю с обычной формы, так как применение processmessages не всегда допустимо, и при долгом процессе часто бывает, что форма прогресса не реагирует на вызов 'hide', оставаясь 'висеть'.
Может быть кто-то видел готовое решение, скорее всего, как я понял, на API. Несколько часов гугленья не принесли результата.
Leonid Troyanovsky (2008-07-31 08:44:00) [1]
> Дмитрий Белькевич © (30.07.08 20:27) [53]
> Интересны новые решения, но не старые догмы.
Решение старо как мир: клиент-сервер.
--
Regards, LVT.
Сергей М. (2008-07-30 20:38:00) [2]
> При диспетчиризации часть очереди будет потеряна
Чавой-то ?!
Фигню ты сейчас сморозил.
WinAPI-диспетчеризация - это, если угодно и дословно, вызов DispatchMessage.
Функция диспетчеризации требует параметром ужевыбранное из очереди сообщение.
С удалением или без удаления из очереди оно тобой получено для возможной его передачи параметром данной ф-йии - это абсолютно не важно, это не забота данной функции.
Т.е. диспетчеризация не имеет никакого отношения к каким-то там "потерям" - у нее иные задачи.
Дмитрий Белькевич (2008-07-30 20:27:00) [3]Извиняюсь, что долго не отвечал - был в отъезде.
>Выбери из очереди и диспетчеризуй только клавиатурные события.
При диспетчиризации часть очереди будет потеряна, а это неприемлемо.
>Если нарисовать кнопку отмены и прогресс-бар в окне доп.потока, то это окошко жить будет, а вот основное окно приложения будет висеть. Т.е. его нельзя будет ни сдвинуть ни свернуть. Оно даже перерисовываться не будет.
Это не проблема.
>Я говорю об обработке в главном потоке, без извращений с формой в другом потоке.
Интересны новые решения, но не старые догмы.
Нашел между делом функцию CreateDialog, буду пробовать с её помощью показать диалог, что бы на api с окнами не возиться.
MultIfleX (2008-07-21 06:31:00) [4]
> Дмитрий Белькевич ©
Тебе надо чтобы пользователь ждал? Так делай Enable формы False, а в конце твоей "длительной обработки" возвращай True, и используй стандартный ProcessMessages, в прЫнципе это для тебя лучший вариант - ни потоков, ни замутов с апи, все просто и функционально. Если это выходит для тебя накладно по времени, попробуй вызывать реже, или по условию что GetTickCount больше "числа Х".
Сергей М. (2008-07-19 11:54:00) [5]
> Что бы оживить GUI (прогресс-форму), мне в основном потоке
> приходится постоянно в цикле обработки вызывать ProcessMessages
Да кто ж тебя заставляет для конкретнойцели использовать ProcessMessages ?
Нужно перерисовать прогресс-бар ? Просто вызови его метод Update.
Нужно знать не нажал ли юзер "отмену" ? Выбери из очереди и диспетчеризуй только клавиатурные события.
Всего делов-то)
GrayFace (2008-07-19 00:47:00) [6]Почему? Я говорю об обработке в главном потоке, без извращений с формой в другом потоке.
Тын-Дын (2008-07-19 00:41:00) [7]
> GrayFace © (18.07.08 22:34) [48]
Бесполезно. TApplication слишком вросся в TForm.
GrayFace (2008-07-18 22:34:00) [8]из [38] возьми вот это и вставь вместо Application.ProcessMessages:
while GetMessage(Msg, FWindow, 0, 0) and not Terminated do begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
FWindow - SuperPuperProgressForm1.Handle. Думаю, поможет.
ANB (2008-07-18 16:54:00) [9]
> Вот. Что бы оживить GUI (прогресс-форму), мне в основном
> потоке приходится постоянно в цикле обработки вызывать ProcessMessages.
>
> Во время вызова, как я пониамаю, обрабатываются все сообщения,
> накопившиеся в очереди. Так вот - иногда в очередь попадают
> нежелательные сообщения, которые должны будут обработаться
> только после того, как основной поток отработает цикл. И
> потерять, или отфильтровать эти сообщения нельзя.
Если нарисовать кнопку отмены и прогресс-бар в окне доп.потока, то это окошко жить будет, а вот основное окно приложения будет висеть. Т.е. его нельзя будет ни сдвинуть ни свернуть. Оно даже перерисовываться не будет.
Тын-Дын (2008-07-18 16:38:00) [10]
> Нет, конечно, иначе бы не было бы никаких вопросов. Поэтому
> нужно простое окно Windows, без VCL'а.
program ProgBar;
//Ляпа ©
uses
Windows,
Messages,
commctrl;
Var Tim:Word;
hProgressBarWnd:HWND;
MaxPos,MinPos:WORD;
function MyWndProc(Window:HWND;Mess:word;
WParam:LongInt;LParam:LongInt):LongInt;stdcall;
begin
case Mess of
WM_CREATE:
begin
hProgressBarWnd:=CreateWindow(PROGRESS_CLASS,'',
WS_CHILD+WS_VISIBLE,
10,75,150,20,window,0,HInstance,nil);
SendMessage(hProgressBarWnd, PBM_SETRANGE,0,MAKELPARAM(MinPos,MaxPos));
SendMessage(hProgressBarWnd,PBM_SETSTEP,1,0);
SendMessage(hProgressBarWnd,PBM_SETPOS,0,0);
Result:=0;
end;
WM_DESTROY:
begin
//Удаляем таймер
KillTimer( Window, 1 );
PostQuitMessage(0);
Result:=0
end;
WM_TIMER:
begin
SendMessage(hProgressBarWnd, PBM_STEPIT, 0, 0);
Result:=0
end;
else Result:=DefWIndowProc(Window,Mess,WParam,LParam)
end;
end;
var
wc:TWndClass;
wnd:HWND;
Msg:TMsg;
PollingDelay:integer=500; //500 - интервал 0.5 сек
begin
//Регистрация класса окна
wc.style:=CS_HREDRAW+CS_VREDRAW;
wc.lpfnWndProc:=@MyWndProc;
wc.cbClsExtra:=0;
wc.cbWndExtra:=0;
wc.hInstance:=HInstance;
wc.hIcon:=LoadIcon(THandle(nil),IDI_APPLICATION);
wc.hCursor:=LoadCursor(THandle(nil),IDC_ARROW);
wc.hbrBackground:=COLOR_BTNFACE+1;//COLOR_WINDOW+1;
wc.lpszMenuName:=nil;
wc.lpszClassName:='MyWndClass';
if (RegisterClass(wc)=0) then exit;
//InitCommonControl(CC: Integer): Boolean; InitCommonControlsEx
InitCommonControls;
MaxPos:=100;
MinPos:=0;
//создаем окно
wnd:=CreateWindow('MyWndClass','Таймер',
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,0,CW_USEDEFAULT,0,
0,0,HInstance,nil);
//Устанавливаем таймер
SetTimer( Wnd, 1, PollingDelay, nil );
//показываем окно
ShowWindow(wnd,SW_RESTORE);
UpdateWindow(wnd);
//начинаем тривиальный цикл сообщений
while GetMessage(Msg,0,0,0) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg)
end
end.
Дмитрий Белькевич (2008-07-18 15:54:00) [11]>когда основной поток сильно грузит компьютер дисковыми операциями.
Когда основной, и он же - единственный... То есть всё - и основная работа и обработка прогресс-форма идёт только в нём. Просто, что бы не подумали, что у меня потоков несколько. Он сейчас один. Собсно, с VCL формой по-другому и не сделать.
Дмитрий Белькевич (2008-07-18 15:51:00) [12]>Игорь Шевченко
Большое спасибо, посмотрю.
>Ведь пока осн.поток занят , GUI мертв !
Вот. Что бы оживить GUI (прогресс-форму), мне в основном потоке приходится постоянно в цикле обработки вызывать ProcessMessages.
Во время вызова, как я пониамаю, обрабатываются все сообщения, накопившиеся в очереди. Так вот - иногда в очередь попадают нежелательные сообщения, которые должны будут обработаться только после того, как основной поток отработает цикл. И потерять, или отфильтровать эти сообщения нельзя.
>в ряде случаев отнюдь не лишнее. Пользователю необязательно видеть изменения прогресса при КАЖДОМ его изменении, иногда достаточно обновления раз в некоторый интервал, в секунду, в две...
Да, знаю такое, но немного по-другому делал. Сейчас есть два метода у формы - Step и Step100, первый обновляет прогрессбар после каждого вызова, второй ведёт внутренний счетчик и обновляет только каждый сотый.
В этом случае, да - таймер тоже хорош.
>Любой, позволяющий получать и диспетчеризовать только нужные (а не все подряд) сообщения - Get/PeekMessage + DispatchMessage
Тут проблема. Сообщения нужны все, но после цикла.
Да и проблема больше не с ними, это я уже научился обрабатывать. Но просто хочется заодно все эти заглушки поубирать. А пробелема с тем, что форма почему-то перестаёт реагировать на вызов Hide. Замечено, что проявляется, когда основной поток сильно грузит компьютер дисковыми операциями.
>Вряд ли можно согласиться с тем, что возможно использовать форму в доп. потоке.
Нет, конечно, иначе бы не было бы никаких вопросов. Поэтому нужно простое окно Windows, без VCL'а.
Тын-Дын (2008-07-18 15:26:00) [13]
> Дмитрий Белькевич © (18.07.08 11:03) [23]
>
> Согласитесь,
> что сделать одно окно проще
Заметь, ты сам написал - " окно"
Вряд ли можно согласиться с тем, что возможно использовать форму в доп. потоке.
Сергей М. (2008-07-18 14:46:00) [14]
> спросил - какой способ лучше?
Любой, позволяющий получать и диспетчеризовать только нужные (а не все подряд) сообщения - Get/PeekMessage + DispatchMessage
> как в отдельном потоке - конечно, Application.ProcessMessages
> не нужен, от него и избавляемся.
>
Да не то что не нужен - не допустим !
Но я, вообще-то, об основном, в котором Application.ProcessMessages допустим по логике VCL, но при этом якобы недопустим по твоей прикладной логике. Вот про возможные его альтернативы я и повел речь.
Игорь Шевченко (2008-07-18 14:45:00) [15]
> Таймер - лишнее, конечно. PostThreadMessage.
в ряде случаев отнюдь не лишнее. Пользователю необязательно видеть изменения прогресса при КАЖДОМ его изменении, иногда достаточно обновления раз в некоторый интервал, в секунду, в две...
Сергей М. (2008-07-18 14:40:00) [16]Я вообще не понимаю, зачемпри данных условиях нужен доп.поток и почему осн.поток не должен "отвлекаться" .. Ведь пока осн.поток занят , GUI мертв ! Ни форму двинуть, ни приложение терминировать, ни картинку перерисовать .."Живое" окно доп.потока с прогресс-баром и кнопкой "Отмена" в расчет не берем - это, конечно, тоже GUI, но совершенно бестолковый, "корове седло")
Дмитрий Белькевич (2008-07-18 14:37:00) [17]>А форма с прогрессом будет, наверно, по таймеру опрашивать эти переменные)
Таймер - лишнее, конечно. PostThreadMessage.
>А причем здесь Application.ProcessMessages ?
Использование оного по твоему же условию невозможно - окно с прогресс-баром должно быть создано в контексте дополнительного потока...
Я думал, что от это вот было сказано по поводу работы с VCL, без доролнительных потоков:
>Вызов этого метода - не единственный и не лучший способ реакции на сообщения.
И спросил - какой способ лучше?
Так как в отдельном потоке - конечно, Application.ProcessMessages не нужен, от него и избавляемся.
Игорь Шевченко (2008-07-18 14:30:00) [18]Я делал где-то так:
основная форма:
unit main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, StdCtrls, ProgressThread;
type
TfMain = class(TForm)
Button: TButton;
Timer: TTimer;
procedure ButtonClick(Sender: TObject);
procedure TimerTimer(Sender: TObject);
private
FProgressThread: TProgressThread;
procedure LongProcessStarted;
procedure LongProcessCompleted;
end;
var
fMain: TfMain;
implementation
{$R *.DFM}
procedure TfMain.ButtonClick(Sender: TObject);
begin
LongProcessStarted;
Timer.Enabled := true;
end;
procedure TfMain.LongProcessCompleted;
begin
FProgressThread.Terminate;
FProgressThread.WaitFor;
FProgressThread.Free;
end;
procedure TfMain.LongProcessStarted;
begin
FProgressThread := TProgressThread.Create (Self);
end;
procedure TfMain.TimerTimer(Sender: TObject);
begin
LongProcessCompleted;
end;
end.
поток с окном (дочерним по отношению к основной форме)
unit ProgressThread;
interface
uses
Windows, Forms, Classes;
type
TProgressThread = class(TThread)
private
FTop, FLeft: Integer;
FParentWidth, FParentHeight: Integer;
FWindow: HWND;
FParentWindow: HWND;
function InitProgressThread: Boolean;
protected
procedure Execute; override;
public
constructor Create (ParentForm: TForm);
destructor Destroy; override;
end;
implementation
uses
SysUtils, Messages;
function ThreadWndProc (Wnd: HWND; Message: UINT; wParam: WPARAM;
lParam: LPARAM): LRESULT; stdcall;
var
ps: PAINTSTRUCT;
rctext: TRect;
begin
case Message of
WM_TIMER:
begin
if wParam = 1000 then begin
InvalidateRect (Wnd, nil, false);
Result := 0;
end else
Result := DefWindowProc (Wnd, Message, wParam, lParam);
end;
WM_DESTROY:
begin
KillTimer(Wnd, 1000);
PostQuitMessage(0);
Result := 0;
end;
WM_PAINT:
begin
BeginPaint(Wnd, ps);
SetTextColor(ps.hdc, 0);
GetClientRect(Wnd, rctext);
DrawText (ps.hdc, PChar(Format('running %d', [GetTickCount])), -1,
rctext, DT_CENTER or DT_VCENTER);
EndPaint(Wnd, ps);
Result := 0;
end;
else
Result := DefWindowProc (Wnd, Message, wParam, lParam);
end;
end;
function TProgressThread.InitProgressThread: Boolean;
const
Height = 100;
Width = 200;
var
wc: WNDCLASS;
begin
FillChar (wc, SizeOf(wc), 0);
wc.lpfnWndProc := @ThreadWndProc;
wc.lpszClassName := 'HSProgressWindowClass';
wc.hInstance := HInstance;
wc.hbrBackground := GetStockObject(WHITE_BRUSH);
Result := Windows.RegisterClass(wc) <> 0;
if not Result then
Exit;
FWindow := CreateWindowEx (0, 'HSProgressWindowClass', 'ProgressWindow',
WS_CHILD or WS_VISIBLE or WS_BORDER, (FParentWidth - Width) div 2,
(FParentHeight - Height) div 2, Width, Height, FParentWindow, 1,
HInstance, Self);
Result := IsWindow(FWindow);
if not Result then
Exit;
SetTimer(FWindow, 1000, 500, nil);
UpdateWindow(FWindow);
end;
{ TProgressThread }
constructor TProgressThread.Create(ParentForm: TForm);
begin
FreeOnTerminate := false;
FLeft := ParentForm.Left;
FTop := ParentForm.Top;
FParentWidth := ParentForm.Width;
FParentHeight := ParentForm.Height;
FParentWindow := ParentForm.Handle;
inherited Create (false);
end;
procedure TProgressThread.Execute;
var
Msg: TMsg;
begin
if not InitProgressThread then
Exit;
while GetMessage(Msg, FWindow, 0, 0) and not Terminated do begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
if Terminated then
DestroyWindow(FWindow);
end;
destructor TProgressThread.Destroy;
begin
Windows.UnregisterClass('HSProgressWindowClass', HInstance);
inherited;
end;
end.
Сергей М. (2008-07-18 14:24:00) [19]
> будет оповещать поток с формой отображения каким-то образом
> о собственно прогрессе
Поди тоже через переменные, раз ему "отвлекаться" нельзя)
А форма с прогрессом будет, наверно, по таймеру опрашивать эти переменные)
Жесть !)
Сергей М. (2008-07-18 14:22:00) [20]
> как основной поток узнает, что ему надо остановить длинный
> режим ?
Он же говорит - осн.поток будет опрашивать флаг.