#delphi #timer
Зависают таймеры при удержании кнопок свернуть, развернуть, закрыть. Т.е. можно остановить таймер, нажав и удерживая кнопку. procedure TForm1.Timer1Timer(Sender: TObject); begin n := n +1; Caption := IntToStr(n); end; Как с этим можно бороться?
Ответы
Ответ 1
Ну, как вариант: сделать свой таймер через отдельный поток, тогда сообщения WM_TIMER будут накапливаться и при отпускании кнопки всё накопленное вывалится разом. Вообще такие таймеры я когда-то даже видел готовые в виде компонент, но простейший пример будет выглядеть так: unit tmMain; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private StopTimer: PBoolean; procedure WMTimerProc(var Message: TWMTimer); message WM_TIMER; public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} type PTimerData = ^TTimerData; TTimerData = record Owner: THandle; Interval: Cardinal; Terminated: Boolean; end; function TimerProc(Param: PTimerData): DWORD; stdcall; begin while not Param.Terminated do begin Sleep(Param.Interval); PostMessage(Param.Owner, WM_TIMER, 0, 0); end; Dispose(Param); end; function StartTimer(Interval: Cardinal; Wnd: THandle): PBoolean; var TimerData: PTimerData; ThreadID: Cardinal; begin if Interval < 10 then raise Exception.Create('Timer interval is too small'); New(TimerData); TimerData.Owner := Wnd; TimerData.Interval := Interval; TimerData.Terminated := False; Result := @TimerData^.Terminated; if CreateThread(nil, 0, @TimerProc, TimerData, 0, ThreadID) = 0 then RaiseLastOSError; end; { TForm1 } procedure TForm1.Button1Click(Sender: TObject); begin StopTimer := StartTimer(500, Handle); end; procedure TForm1.Button2Click(Sender: TObject); begin StopTimer^ := True; end; procedure TForm1.WMTimerProc(var Message: TWMTimer); begin Caption := IntToStr(GetTickCount); end; end. P.S: if Interval < 10 then из-за того что разрешающая способность Sleep() где-то 8-15 мс.Ответ 2
Delphi у меня под рукой нет, поэтому опишу на примере платформы .NET. Любопытно. Попробовал в C# WinForms с применением System.Windows.Forms.Timer - аналогичное поведение. Дело тут вот в чём: событие тика таймера происходит в гуевом потоке. Событие нажатия на эти кнопки - тоже в потоке GUI. Кнопку нажали - произошёл вход в обработчик события - происходит ожидание отжатия - всё встало. Взял взамен System.Threading.Timer. Событие тика этого типа таймера происходит не в том потоке, в котором работает форма, а в отдельном потоке, взятом из пула. Однако, к компонентам формы можно обращаться только из того потока, в котором они созданы. Поэтому для обновления, в частности, заголовка формы нужно использовать Control.Invoke. При этом происходит передача управления в поток GUI - снова всё зависает. В C# удалось побороть это используя System.Threading.Timer и Control.BeginInvoke. Этот метод вызывается асинхронно и хотя прорисовка формы при зажатии кнопки по-прежнему не происходит, но инкремент переменной n продолжается. И когда кнопку отпускаем, выводится увеличенное значение.Ответ 3
Добавить в описании класса формы: procedure WmNcLButtonDown(var Msg:TMessage); message WM_NCLBUTTONDOWN; procedure WmNcRButtonDown(var Msg:TMessage); message WM_NCRBUTTONDOWN; с такой реализацией: procedure TForm1.WmNcLButtonDown(var Msg: TMessage); begin Msg.Result := 0; case Msg.WParam of HTCLOSE: PostMessage(Handle, WM_SYSCOMMAND, SC_CLOSE, 0); HTMINBUTTON: PostMessage(Handle, WM_SYSCOMMAND, SC_MINIMIZE, 0); HTMAXBUTTON: if ( WindowState = wsMaximized ) then PostMessage(Handle, WM_SYSCOMMAND, SC_RESTORE, 0) else PostMessage(Handle, WM_SYSCOMMAND, SC_MAXIMIZE, 0); else Msg.Result := DefWindowProc(Handle, Msg.Msg, Msg.WParam, Msg.LParam); end; end; procedure TForm1.WmNcRButtonDown(var Msg: TMessage); begin Msg.Result := 0; case Msg.WParam of HTMINBUTTON, HTCAPTION, HTSYSMENU: PostMessage(Handle, WM_CONTEXTMENU, Msg.WParam, Msg.LParam); else Msg.Result := DefWindowProc(Handle, Msg.Msg, Msg.WParam, Msg.LParam); end; end; Теперь удерживать кнопку не получится - действие будет сразу по нажатию.Ответ 4
На английском для шарпа(но по сути для всего) ответ тут По русский Происходит это потому что пока нажата хотя бы одна из этих кнопок главный поток блокируется. Соответственно сообщения таймера не придет. Проверяется очень легко через переопределение WndProc с OutoutDebugString. Если при это все равно очень нужен таймер, то нужно делать его на потоке и при этом не забывать, что гуй залочен.
Комментариев нет:
Отправить комментарий