13

質問者様のおっしゃる通りです。TTimerDelphi に、変数を含むコンソール アプリケーションがあります。私がやりたいことは、イベントハンドラーをTTimer.OnTimerイベントに割り当てることです。私は Delphi にまったく慣れていません。以前は C# を使用していましたが、イベント ハンドラをイベントに追加する方法はまったく異なります。単純にプロシージャをハンドラとしてイベントに割り当てるのではなく、ハンドラとなるメソッドを持つダミー クラスを作成してから、このメソッドをイベントに割り当てる必要があることがわかりました。ここに私が現在持っているコードがあります:

program TimerTest;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  extctrls;

type
  TEventHandlers = class
    procedure OnTimerTick(Sender : TObject);
  end;

var
  Timer : TTimer;
  EventHandlers : TEventHandlers;


procedure TEventHandlers.OnTimerTick(Sender : TObject);
begin
  writeln('Hello from TimerTick event');
end;

var
  dummy:string;
begin
  EventHandlers := TEventHandlers.Create();
  Timer := TTimer.Create(nil);
  Timer.Enabled := false;
  Timer.Interval := 1000;
  Timer.OnTimer := EventHandlers.OnTimerTick;
  Timer.Enabled := true;
  readln(dummy);
end.

私には正しいように思えますが、何らかの理由で機能しません。

EDITコンソールアプリケーションにはメッセージループがないため
、コンポーネントが機能しないようです。TTimerアプリケーションでタイマーを作成する方法はありますか?

4

3 に答える 3

19

TTimerコンポーネントが内部的WM_TIMERにメッセージ処理を使用し、コンソール アプリにメッセージ ループがないため、コードが機能しません。コードを機能させるには、メッセージ ポンピング ループを自分で作成する必要があります。

program TimerTest;

{$APPTYPE CONSOLE}

uses
  SysUtils, Windows,
  extctrls;

type
  TEventHandlers = class
    procedure OnTimerTick(Sender : TObject);
  end;

var
  Timer : TTimer;
  EventHandlers : TEventHandlers;


procedure TEventHandlers.OnTimerTick(Sender : TObject);
begin
  writeln('Hello from TimerTick event');
end;

procedure MsgPump;
var
  Unicode: Boolean;
  Msg: TMsg;

begin
  while GetMessage(Msg, 0, 0, 0) do begin
    Unicode := (Msg.hwnd = 0) or IsWindowUnicode(Msg.hwnd);
    TranslateMessage(Msg);
    if Unicode then
      DispatchMessageW(Msg)
    else
      DispatchMessageA(Msg);
  end;
end;

begin
  EventHandlers := TEventHandlers.Create();
  Timer := TTimer.Create(nil);
  Timer.Enabled := false;
  Timer.Interval := 1000;
  Timer.OnTimer := EventHandlers.OnTimerTick;
  Timer.Enabled := true;
  MsgPump;
end.
于 2012-08-19T13:53:08.843 に答える
19

他の人が述べたように、コンソール アプリケーションにはメッセージ ポンプがありません。

TConsoleTimerこれは、クラスを模倣するスレッド クラスですTTimer。主な違いは、イベント内のコードがTConsoleTimerスレッドで実行されることです。

アップデート

この投稿の最後に、このイベントをメイン スレッドで呼び出す方法があります。

unit ConsoleTimer;

interface

uses
  Windows, Classes, SyncObjs, Diagnostics;

type
  TConsoleTimer = Class(TThread)
  private
    FCancelFlag: TSimpleEvent;
    FTimerEnabledFlag: TSimpleEvent;
    FTimerProc: TNotifyEvent; // method to call
    FInterval: integer;
    procedure SetEnabled(doEnable: boolean);
    function GetEnabled: boolean;
    procedure SetInterval(interval: integer);
  protected
    procedure Execute; override;
  public
    Constructor Create;
    Destructor Destroy; override;
    property Enabled : boolean read GetEnabled write SetEnabled;
    property Interval: integer read FInterval write SetInterval;
    // Note: OnTimerEvent is executed in TConsoleTimer thread
    property OnTimerEvent: TNotifyEvent read FTimerProc write FTimerProc;
  end;

implementation

constructor TConsoleTimer.Create;
begin
  inherited Create(false);
  FTimerEnabledFlag := TSimpleEvent.Create;
  FCancelFlag := TSimpleEvent.Create;
  FTimerProc := nil;
  FInterval := 1000;
  Self.FreeOnTerminate := false; // Main thread controls for thread destruction
end;

destructor TConsoleTimer.Destroy; // Call TConsoleTimer.Free to cancel the thread
begin
  Terminate; 
  FTimerEnabledFlag.ResetEvent; // Stop timer event
  FCancelFlag.SetEvent; // Set cancel flag
  Waitfor; // Synchronize
  FCancelFlag.Free;
  FTimerEnabledFlag.Free;
  inherited;
end;

procedure TConsoleTimer.SetEnabled(doEnable: boolean);
begin
  if doEnable then
    FTimerEnabledFlag.SetEvent
  else
    FTimerEnabledFlag.ResetEvent;
end;

procedure TConsoleTimer.SetInterval(interval: integer);
begin
  FInterval := interval;
end;

procedure TConsoleTimer.Execute;
var
  waitList: array [0 .. 1] of THandle;
  waitInterval,lastProcTime: Int64;
  sw: TStopWatch;
begin
  sw.Create;
  waitList[0] := FTimerEnabledFlag.Handle;
  waitList[1] := FCancelFlag.Handle;
  lastProcTime := 0;
  while not Terminated do
  begin
    if (WaitForMultipleObjects(2, @waitList[0], false, INFINITE) <>
      WAIT_OBJECT_0) then
      break; // Terminate thread when FCancelFlag is signaled
    if Assigned(FTimerProc) then
    begin
      waitInterval := FInterval - lastProcTime;
      if (waitInterval < 0) then
        waitInterval := 0;
      if WaitForSingleObject(FCancelFlag.Handle,waitInterval) <> WAIT_TIMEOUT then
        break;

      if WaitForSingleObject(FTimerEnabledFlag.Handle, 0) = WAIT_OBJECT_0 then
      begin
        sw.Start;
        FTimerProc(Self);
        sw.Stop;
        // Interval adjusted for FTimerProc execution time
        lastProcTime := sw.ElapsedMilliSeconds;
      end;
    end;
  end;
end;

function TConsoleTimer.GetEnabled: boolean;
begin
  Result := (FTimerEnabledFlag.Waitfor(0) = wrSignaled);
end;

end.

そしてテスト:

program TestConsoleTimer;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,ConsoleTimer;

type
  TMyTest = class
    procedure MyTimerProc(Sender: TObject);
  end;

procedure TMyTest.MyTimerProc(Sender: TObject);
begin
  // Code executed in TConsoleTimer thread !
  WriteLn('Timer event');
end;

var
  MyTest: TMyTest;
  MyTimer: TConsoleTimer;
begin
  MyTest := TMyTest.Create;
  try
    MyTimer := TConsoleTimer.Create;
    MyTimer.Interval := 1000;
    MyTimer.OnTimerEvent := MyTest.MyTimerProc;
    WriteLn('Press [Enter] key to end.');
    MyTimer.Enabled := true;
    ReadLn;
    MyTimer.Free;
  finally
    MyTest.Free;
    WriteLn('End.');
  end;
end.

前述のように、メイン スレッドでイベントを実行するにはどうすればよいですか?

Delphi 7: コンソール アプリケーションでのイベントの処理 (TidIRC)を読むと答えが得られます。

にメソッドを追加しTConsoleTimerます。

procedure TConsoleTimer.SwapToMainThread;
begin
  FTimerProc(Self);
end;

Executeメソッドの呼び出しを次のように変更します。

Synchronize(SwapToMainThread);

同期された呼び出しをポンピングするにはCheckSynchronize()、Classes ユニットで関数を使用します。

while not KeyPressed do CheckSynchronize(); // Pump the synchronize queue

注: コンソールKeyPressed関数はここにあります: How i can implement a IsKeyPressed function in a delphi console application? .

于 2012-08-19T16:11:38.420 に答える
5

コンソール アプリケーションにはメッセージ ポンプはありませんが、スレッドはあります。作業を行い、作業が完了したときに次の秒を待つスレッドを作成すると、必要な結果が得られるはずです。専用スレッドの作成方法については、TThread に関するドキュメントを参照してください。ただし、スレッドとの間でデータを取得するのはそれほど簡単ではありません。そのため、OmniThreadLibrary など、これを支援する「生の」TThread の代替手段が多数あります。

于 2012-08-19T14:40:18.023 に答える