SetTimer Windows APIを利用したタイマーコンポーネントの作成 (コールバック関数呼び出し版) - C#

タイマー処理をコンポーネント化して手軽に扱えるようにしたコードを紹介します。
SetTimer Windows APIを利用してタイマーを実装する」の記事では、SetTimer関数を呼び出してタイマーを実装するコードを紹介しましたが、この方法をそのまま用いると、コンポーネントでウィンドウメッセージを受け取る必要があり、コンポーネントをControlの派生として作成する必要があります。Controlの派生として作成するとフォーム内に配置されてしまうため、見栄えが良くありません。ここでは、コンポーネントをタイマーイベントをコールバック関数を受け取る方法で実装します。

コンポーネント部コード

以下のコードを記述します。

iPentecTimer.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;

namespace iPentecTimer
{
  public partial class iPentecTimer : Component
  {
    public int Interval { set; get; }
    public bool Enabled { 
      set { 
        _enabled =value;
        UpdateTimer();
      }
      get
      {
        return _enabled;
      }
    }

    private bool _enabled=false;

    [DllImport("user32.dll", ExactSpelling = true)]
    static extern IntPtr SetTimer(IntPtr hWnd, IntPtr nIDEvent, uint uElapse, TimerProc lpTimerFunc);
    delegate void TimerProc(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime);

    [DllImport("user32.dll", ExactSpelling = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool KillTimer(IntPtr hWnd, IntPtr uIDEvent);
    
    public delegate void OnTimerDelegate();
    private OnTimerDelegate onTimer;


    public iPentecTimer()
    {
      InitializeComponent();
      Interval = 1000;
    }

    public iPentecTimer(IContainer container)
    {
      container.Add(this);

      InitializeComponent();
      Interval = 1000;
    }

    protected void UpdateTimer()
    {
      KillTimer(IntPtr.Zero, IntPtr.Zero);
      if (Interval != 0) {
        if (Enabled == true){
          TimerProc tp = timerProc;
          if (SetTimer(IntPtr.Zero, IntPtr.Zero, (uint)Interval, tp) == (IntPtr)0) {
          }
        }
      }
    }

    private void timerProc(IntPtr hWnd, uint uMsg, IntPtr nIDEvent, uint dwTime)
    {
      if (onTimer != null) {
        onTimer();
      }
    }

    public event OnTimerDelegate OnTimer
    {
      add
      {
        onTimer += value;
      }
      remove
      {
        onTimer -= value;
      }
    }
  }
}

解説

SetTimer関数の第四引数にコールバック関数のデリゲートを与えます。下記のコードでは、timerProcメソッドがコールバックにより、呼び出される動作になります。
protected void UpdateTimer()
{
  KillTimer(IntPtr.Zero, IntPtr.Zero);
  if (Interval != 0) {
    if (Enabled == true){
      TimerProc tp = timerProc;
      if (SetTimer(IntPtr.Zero, IntPtr.Zero, (uint)Interval, tp) == (IntPtr)0) {
      }
    }
  }
}

コンポーネント利用アプリ プログラム

UI

下図のUIを作成します。


Timerコンポーネントはタイマーコンポーネントのプロジェクトを参照するとツールパレットに表示されます。(下図参照)

コード

以下のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace iPentecTimerDemo
{
  public partial class FormMain : Form
  {
    int count=0;

    public FormMain()
    {
      InitializeComponent();
    }

    private void iPentecTimer1_OnTimer()
    {
      textBox1.Text += string.Format("+:{0:d}\r\n",count);
      count++;
    }

    private void button1_Click(object sender, EventArgs e)
    {
      textBox1.Text += "Start\r\n";
      iPentecTimer1.Enabled = true;
    }

    private void button2_Click(object sender, EventArgs e)
    {
      textBox1.Text += "Stop\r\n";
      iPentecTimer1.Enabled = false;
    }
  }
}

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


[Start]ボタンをクリックします。タイマーが開始され、1秒ごとにTextBoxにメッセージが表示されます。



[Stop]ボタンをクリックするとタイマーの処理が停止します。

参考 / 補足

SetTimer関数を用いてウィンドウメッセージをキャプチャーする方式でコンポーネントを実装することもできます。ウィンドウメッセージをキャプチャーする方式のTimerコンポーネントは「SetTimer Windows APIを利用したタイマーコンポーネントの作成 (ウィンドウメッセージ取得版)」の記事を参照してください。
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2014-05-26
iPentec all rights reserverd.