タイマを使って一定間隔でイベントを実行する

Windows Forms アプリケーションは System.Windows.Forms.Timer というクラスを使って一定間隔で発生するイベントを簡単に記述することができました。

WPF アプリケーションの場合は System.Windows.Threading.DispatcherTimer というクラスを使うのがおすすめです。タイマ間隔はナノ秒間隔で設定できるそうですが本当なのでしょうか。懐疑的になってしまいますが信じることしましょう。

厳密な等時間性を気にするアプリケーションでは RT-Linux や VxWorks などをチョイスしたほうがいいでしょう。

本記事で示すサンプルソースは、1秒ごとにウインドウに現在の時分秒ミリ秒を表示します。まずは XAML です。静的な描画アイテムのテキストブロックをあらかじめ定義しておきます。

<Window x:Class="aaa.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:aaa"
        mc:Ignorable="d"
        Title="MainWindow" Height="320" Width="480">

    <DockPanel>

        <Menu DockPanel.Dock="Top">
            <MenuItem x:Name="menuApplication" Header="Application">
                <MenuItem Header= "Quit" Click="menuApplicationQuit_Click" InputGestureText="Ctrl+Q"/>
            </MenuItem>

            <MenuItem x:Name="menuTimer" Header="Timer">
                <MenuItem x:Name="menuTimerStart" Header="Start" Click="menuTimerStart_Click"/>
                <MenuItem x:Name="menuTimerStop" Header="Stop" Click="menuTimerStop_Click"/>
            </MenuItem>
            
        </Menu>

        <ToolBarTray DockPanel.Dock="Top">
            <ToolBar>
                <Button x:Name="tbnApplicationQuit" Content="Quit" Width="64" Height="64" Click="menuApplicationQuit_Click"/>
                <Separator/>
                <Button x:Name="tbnTimerStart" Content="Start" Width="64" Height="64" Click="menuTimerStart_Click"/>
                <Button x:Name="tbnTimerStop"  Content="Stop"  Width="64" Height="64" Click="menuTimerStop_Click"/>
            </ToolBar>
        </ToolBarTray>

        <StatusBar x:Name="the_stb" DockPanel.Dock="Bottom" Height="30">
            <Label x:Name="the_stb_label" />
        </StatusBar>

        <Grid x:Name="the_grid" Background="DarkViolet">
            <Canvas x:Name="the_canvas" Background="Transparent">
                <TextBlock x:Name="the_tbk" FontSize="36" Foreground="White" Background="Transparent"/>
            </Canvas>
        </Grid>

    </DockPanel>

</Window>

つぎに xaml.cs で簡単に動作を説明します。

31~40行目で、一定間隔で実施したいことを記述します。この例では現在の時分秒を取得しています。

58行目、タイマの間隔を設定します。ナノ秒単位なので、即値で設定するのは数値が大きくなりすぎるので、ミリ秒から計算して設定しています。ゼロがいっぱい並んでもいいのならば即値で設定してもかまいません。

61行目、タイマと実行したいイベントを関連付けます。

64行目、実行したいイベントを関連付けただけではイベントが実施されない仕様ですので、Start を明示することで動作を開始させます。

using System.Windows.Threading; の追加をお忘れなく.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

// これの追加を忘れないように. Don't forget to add this sentence.
using System.Windows.Threading;

namespace aaa
{

	public partial class MainWindow : Window
	{

		const long TICKS_MSEC = 1000;

		DispatcherTimer MyTimer;

		DateTime Dtm;

		private void my_timer_method(object sender, EventArgs e)
		{

			// 現在の時間をクラスグローバルの変数に代入する.
			Dtm = DateTime.Now;

			// 再描画を要請する(OnRender実行).
			this.InvalidateVisual();

		}

		/////////////////////////////////////////////////////////
		/////////////////////////////////////////////////////////
		/////////////////////////////////////////////////////////

		public MainWindow()
		{

			InitializeComponent();

			MyTimer = new DispatcherTimer();

			// タイマ間隔を計算する.
			long ticks_usec = TICKS_MSEC * 1000;
			long ticks_nsec100 = ticks_usec * 10;

			// 100nsec単位の ticks で設定する.
			MyTimer.Interval = new TimeSpan( ticks_nsec100 );

			// 実行するメソッドを登録する.
			MyTimer.Tick += new EventHandler( my_timer_method );

			// タイマ実行を開始する.
			MyTimer.Start();

			this.Title = "GazoYaro";

		}

		private void menuApplicationQuit_Click( object sender, RoutedEventArgs e )
		{
			this.Close();
		}

		protected override void OnRender( DrawingContext dc )
		{

			int h2 = Dtm.Hour;
			int m2 = Dtm.Minute;
			int s2 = Dtm.Second;
			int n3 = Dtm.Millisecond;

			// テキストブロックに表示したい文字列を作る.
			const String FMT = "{0:d2}:{1:d2}:{2:d2}.{3:d3}";
			String s = String.Format( FMT, h2, m2, s2, n3 );

			// テキストブロックに文字列を表示する.
			the_tbk.Text = s;

			// キャンバスの幅高サイズ取得する.
			double canvas_acw = the_canvas.ActualWidth;
			double canvas_ach = the_canvas.ActualHeight;

			// テキストブロックの幅高サイズ取得する.
			double tbk_w = the_tbk.ActualWidth;
			double tbk_h = the_tbk.ActualHeight;

			// テキストブロックの表示位置をまんなかにする.
			double x = (( canvas_acw * 0.5 ) - ( tbk_w * 0.5 ));
			double y = (( canvas_ach * 0.5 ) - ( tbk_h * 0.5 ));
			Canvas.SetLeft( the_tbk, x );
			Canvas.SetTop( the_tbk, y );

			// ステータスバーのラベルに文字列を表示する.
			the_stb_label.Content = s;

			// タイマが実行されているかどうかでステータスバーの色を変える.
			if ( MyTimer.IsEnabled )
			{
				the_stb.Background = Brushes.Aqua;
			}
			else
			{
				the_stb.Background = Brushes.Transparent;
			}

		}

		private void menuTimerStart_Click( object sender, RoutedEventArgs e )
		{

			// タイマを開始する.
			MyTimer.Start();

			// 再描画を要請する.
			this.InvalidateVisual();

		}

		private void menuTimerStop_Click( object sender, RoutedEventArgs e )
		{

			// タイマを停止する.
			MyTimer.Stop();

			// 再描画を要請する.
			this.InvalidateVisual();

		}

	}

}

サンプルソースを用意しました、上記のソースコードのコピーペーストでうまくビルドできない場合などにご利用ください。