マルチメディアタイマーを利用する - マルチメディアタイマーコンポーネントの作成 - C#

マルチメディアタイマーを利用したタイマーのコードを紹介します。

UI

下図のUIを用意します。フォームにテキストボックスとボタンを配置します。また、コンポーネントとして作成したMultiMediaTimerComponentを配置します。MultiMediaTimerComponentのプロパティは下図のとおりです。

コード

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

MultiMediaTimerComponent.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;

namespace MultimediaTimerDemo
{
  public partial class MultiMediaTimerComponent : Component
  {
    [DllImport("winmm.dll", SetLastError = true)]
    static extern UInt32 timeSetEvent(UInt32 msDelay, UInt32 msResolution,
      TimerEventHandler handler, UIntPtr userCtx, UInt32 eventType);

    [DllImport("winmm.dll", SetLastError = true)]
    static extern UInt32 timeKillEvent(UInt32 timerEventId);

    [DllImport("winmm.dll", SetLastError = true)]
    public static extern UInt32 timeBeginPeriod(UInt32 uMilliseconds);
    //public static extern uint timeBeginPeriod(uint uMilliseconds);

    [DllImport("winmm.dll", SetLastError = true)]
    public static extern uint timeEndPeriod(UInt32 uMilliseconds);

    const int TIMERR_NOERROR = 0;
    //---
    [DllImport("winmm.dll", SetLastError = true)]
    static extern UInt32 timeGetDevCaps(ref TimeCaps timeCaps, UInt32 sizeTimeCaps);

    [StructLayout(LayoutKind.Sequential)]
    public struct TimeCaps
    {
      public UInt32 wPeriodMin;
      public UInt32 wPeriodMax;
    };

    private delegate void TimerEventHandler(UInt32 id, UInt32 msg, UIntPtr userCtx,
      UIntPtr rsv1, UIntPtr rsv2);

    private UInt32 uTimerID;
    private bool iEnabled;
    private uint iInterval;
    private uint iResolution;
    TimerEventHandler TimerHandler;

    public bool Enabled
    {
      get
      {
        return iEnabled;
      }
      set
      {
        //値が異なる場合のみ設定
        if (iEnabled != value) {
          iEnabled = value;
          UpdateTimeEvent();
        }
      }
    }

    public uint Interval
    {
      get
      {
        return iInterval;
      }
      set
      {
        if (iResolution > value) {
          iResolution = value;
        }

        //値が異なる場合のみ設定
        if (iInterval != value) {
          iInterval = value;
          UpdateTimeEvent();
        }
      }
    }

    public uint Resolution
    {
      get
      {
        return iResolution;
      }
      set
      {
        TimeCaps timeCaps = new TimeCaps();
        UInt32 DevCaps = timeGetDevCaps(ref timeCaps, (uint)Marshal.SizeOf(timeCaps));
        if (value > timeCaps.wPeriodMax) {
          iResolution = timeCaps.wPeriodMax;
        }
        if (value < timeCaps.wPeriodMin) {
          iResolution = timeCaps.wPeriodMin;
        }
        if (value > iInterval) {
          iResolution = iInterval;
        }

        //値が異なる場合のみ設定
        if (iResolution != value){
          iResolution = value;
          UpdateTimeEvent();
        }
      }
    }

    public delegate void TimerDelegate(object sender);
    private TimerDelegate onTimer;
    public event TimerDelegate OnTimer
    {
      add
      {
        onTimer += value;
      }
      remove
      {
        onTimer -= value;
      }
    }
    
    public MultiMediaTimerComponent()
    {
      InitializeComponent();
      initProc();
    }

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

      InitializeComponent();
      initProc();
    }

    private void initProc(){
      iInterval = 1000;
      iResolution = 1000;
      iEnabled = true;
      uTimerID = 0;

      TimerHandler = TimerProc;
      UpdateTimeEvent();
    }

    public void UpdateTimeEvent(){
      UInt32 msDelay;
      UInt32 msResolution;
      uint uKillTimer;
      uint uEndPeriod;
      UIntPtr UserCtx = UIntPtr.Zero;

      if (iEnabled == true && iInterval > 0) {
        if (uTimerID != 0) {
          uKillTimer = timeKillEvent(uTimerID);
        }
        msDelay = (uint)iInterval;
        msResolution = (uint)iResolution;

        if (timeBeginPeriod(msResolution) == TIMERR_NOERROR) {
          uTimerID = timeSetEvent(msDelay, msResolution, TimerHandler, UserCtx, 1);
        }
        else {
          //
        }
      }
      else {
        if (uTimerID != 0) {
          uKillTimer = timeKillEvent(uTimerID);

          msResolution = (uint)iResolution;
          uEndPeriod = timeEndPeriod(msResolution);
        }
      }
    }

    private void TimerProc(UInt32 id, UInt32 msg, UIntPtr userCtx, UIntPtr rsv1, UIntPtr rsv2)
    {
      if (onTimer != null) {
        onTimer(this);
      }

      /*
      //クリティカルセクションでイベントハンドラを実行する場合
      object syncObject = new object();
      bool lockTaken = false;

      try {
        Monitor.Enter(syncObject, ref lockTaken);
        if (onTimer != null) {
          
          //onTimer(this);
        }
      }
      finally {
        if (lockTaken ==true) Monitor.Exit(syncObject);
      }
      */ 
    }
  }
}

解説

128行目付近まではWindows APIの宣言、プロパティの宣言(詳細)、イベントの宣言です(詳細,詳細)。

private void initProc(){
  iInterval = 1000;
  iResolution = 1000;
  iEnabled = true;
  uTimerID = 0;

  TimerHandler = TimerProc;
  UpdateTimeEvent();
}
コンポーネント配置、初期化時の設定です。プロパティの初期値などを記述します。

public void UpdateTimeEvent(){
  UInt32 msDelay;
  UInt32 msResolution;
  uint uKillTimer;
  uint uEndPeriod;
  UIntPtr UserCtx = UIntPtr.Zero;

  if (iEnabled == true && iInterval > 0) {
    if (uTimerID != 0) {
      uKillTimer = timeKillEvent(uTimerID);
    }
    msDelay = (uint)iInterval;
    msResolution = (uint)iResolution;

    if (timeBeginPeriod(msResolution) == TIMERR_NOERROR) {
      uTimerID = timeSetEvent(msDelay, msResolution, TimerHandler, UserCtx, 1);
    }
    else {
      //
    }
  }
  else {
    if (uTimerID != 0) {
      uKillTimer = timeKillEvent(uTimerID);

      msResolution = (uint)iResolution;
      uEndPeriod = timeEndPeriod(msResolution);
    }
  }
}
上記がマルチメディアタイマーの設定メソッドです。Enabledプロパティがtrueになっている場合は、タイマーが動作していればタイマーを停止し、その後timeSetEvent()関数を実行しマルチメディアタイマーを開始します。一方、Enabledプロパティがfalseの場合はタイマーが動作していればマルチメディアタイマーを停止します。

private void TimerProc(UInt32 id, UInt32 msg, UIntPtr userCtx, UIntPtr rsv1, UIntPtr rsv2)
{
  if (onTimer != null) {
    onTimer(this);
  }
}
上記のTimerProc()メソッドはマルチメディアタイマーにより呼び出される関数です。メソッド内でイベントハンドラを呼び出します。注意すべきはマルチメディアタイマーによる呼び出しは別スレッドになります。(そのままではフォームのコントロールの操作はできません。)

MultiMediaTimerComponent.Designer.cs

[新しい項目の追加]で[コンポーネントクラス]を選択した場合は自動で作成されます。
namespace MultimediaTimerDemo
{
  partial class MultiMediaTimerComponent
  {
    /// <summary>
    /// 必要なデザイナー変数です。
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary> 
    /// 使用中のリソースをすべてクリーンアップします。
    /// </summary>
    /// <param name="disposing">マネージ リソースが破棄される場合 true、
     ///  破棄されない場合は false です。</param>
    protected override void Dispose(bool disposing)
    {
      if (disposing && (components != null)) {
        components.Dispose();
      }
      base.Dispose(disposing);
    }

    #region コンポーネント デザイナーで生成されたコード

    /// <summary>
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InitializeComponent()
    {
      components = new System.ComponentModel.Container();
    }

    #endregion
  }
}

FormMain.cs (フォーム部分)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace MultimediaTimerDemo
{
  public partial class FormMain : Form
  {
    delegate void UpdateTextBoxDelegate();
    int index = 0;

    public FormMain()
    {
      InitializeComponent();
    }

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

    private void button1_Click(object sender, EventArgs e)
    {
      multiMediaTimerComponent1.Enabled = true;
    }

    private void multiMediaTimerComponent1_OnTimer(object sender)
    {
      //別スレッドの呼び出しになるので注意
       //textBox1.Text += "OnTimer\r\n"; // ← 不可(不正終了する)
      this.Invoke(new UpdateTextBoxDelegate(UpdateTextBox), null);
    }
  }
}

解説

private void multiMediaTimerComponent1_OnTimer(object sender)
{
  //別スレッドの呼び出しになるので注意
   //textBox1.Text += "OnTimer\r\n"; // ← 不可(不正終了する)
  this.Invoke(new UpdateTextBoxDelegate(UpdateTextBox), null);
}
マルチメディアタイマーにより呼び出されるメソッドは別スレッドでの呼び出しとなります。そのため、フォームのコントロールを操作する場合は、Invokeメソッドを用いて操作する必要があります。詳細はこちらの記事を参照してください。

実行結果

アプリケーションを実行しボタンを押すとタイマーが動き出します。一定時間ごとにテキストボックスにメッセージが表示されます。

著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2012-02-13
iPentec all rights reserverd.