System.Timers.Timer 的使用
.Net framwork 提供了許多 Time 方法可以使用,分別是
System.Windows.Threading.DispatcherTimer
在某種程度上用法其實是大同小異的
UI code
<Window x:Class="WpfApplication1.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:WpfApplication1" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded"> <Grid Margin="10"> <Grid.ColumnDefinitions> <ColumnDefinition Width="39*"/> <ColumnDefinition Width="22*"/> </Grid.ColumnDefinitions> <Button x:Name="button" Content="Button" Margin="15,15,10,10" Grid.Column="1" Click="button_Click"/> <Label x:Name="label" Content="秒數倒數" Margin="10"/> </Grid> </Window>
後端Code
using System;
using System.Timers;
using System.Windows;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Timer Timer = new Timer();
int CountDown = 0;
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
CountDown = 3;
label.Content = CountDown;
Timer.Start();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Timer.Interval = 1000;
Timer.Elapsed += new System.Timers.ElapsedEventHandler(TimerTick);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void TimerTick(object sender, System.Timers.ElapsedEventArgs e)
{
try
{
if (CountDown > 0)
{
CountDown--;
label.Content = CountDown;
}
else
{
Timer.Stop();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
}
此範例為秒數倒數程式,當執行倒數時你會發現
「呼叫執行緒無法存取此物件 因為此物件屬於另一個執行緒。」錯誤,
因為在WPF、WinForm這些應用程式中,必需是UI執行緒才能控制表單,
所以說非UI執行緒如果要控制表單,如上例TimerTick(object sender, System.Timers.ElapsedEventArgs e),
必須要將控制表單的程式邏輯封裝成為委派,再將這個委派提交給UI執行緒去執行,
藉由這個流程非UI執行緒就能夠跨執行緒控制表單。
改成如下
using System;
using System.Timers;
using System.Windows;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Timer Timer = new Timer();
int CountDown = 0;
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
CountDown = 3;
label.Content = CountDown;
Timer.Start();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Timer.Interval = 1000;
Timer.Elapsed += new System.Timers.ElapsedEventHandler(TimerTick);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void TimerTick(object sender, System.Timers.ElapsedEventArgs e)
{
try
{
if (CountDown > 0)
{
CountDown--;
Action methodDelegate = delegate ()
{
label.Content = CountDown;
};
this.Dispatcher.BeginInvoke(methodDelegate);
}
else
{
Timer.Stop();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
}
值得注意的是,System.Timers.Timer於跨執行緒更新UI的方式除了上述的委派方式之外,
還可以使用SynchronizingObject屬性來達成,程式碼改成如下
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Timer.Interval = 1000;
Timer.Elapsed += new System.Timers.ElapsedEventHandler(TimerTick);
this.Timer.SynchronizingObject = this;
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
但是這會發生「CS0266 Cannot implicitly convert type
'WpfApplication1.MainWindow' to 'System.ComponentModel.ISynchronizeInvoke'.
An explicit conversion exists (are you missing a cast?)」錯誤,
原因為這種方式只適用在WinForm並不適合用在WPF,
詳細說明可參考WPF window doesn't implement ISynchronizeInvoke.InvokeRequired ERROR
參考資料: