作者:Tony Qu
註:大家可以對本文的寫作風格作一些點評,看看是否喜歡,是否有更好的建議,先謝謝了!
Timer控制項是WinForm開發中必備的控制項之一,在.net 3.0之前的版本中,我們可以很方便的從工具箱中拖出一個Timer來,在組件可視化列表中,我們會看到一個時鐘,這就表示該WinForm中存在一個計時器。
可能是CTP版本的關係,在WPF的VS2005外掛程式的工具箱中,根本無法找到Timer控制項的蹤影。看來我只有自力更生了
既然我們要製作時鐘,首先來畫介面,自然要先定義視窗的XAML,如下:
<Window x:Class="EClock.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="EClock" Height="97" Width="187"
>
<Grid>
<TextBlock Margin="14,11,19,12" Name="textBlock1" Loaded="OnTextBlockLoaded" FontFamily="Time New Roman" FontSize="40">00:00:00</TextBlock>
</Grid>
</Window>
有心人已經應該發現了,這個程式叫做EClock,而這個Window則叫做Window1(可以從x:Class中看出,這是code-behind代碼的引用)
我定義了一個TextBlock,ID為textBlock1。其他的一些基本屬性如FontFamily, FontSize, Margin就不說了,大家都會設定。來說說這個Loaded屬性,目前的WPF還不支援在可視化介面下建置事件處理常式(event handler),所以得自己加,這裡的Loaded就是TextBlock的載入事件,那麼裡面的OnTextBlockLoaded自然就是事件處理常式的名稱。
首先,我在WPF 視窗Window1的Class聲明中定義了一個新的變數,叫做timer,請注意我們並沒有使用System.Timers.Timer,而是使用了DispatcherTimer,至於原因將在本文最後講解。定義代碼如下:
System.Windows.Threading.DispatcherTimer timer;
下面我們來看看這個事件處理常式的內容:
private void OnTextBlockLoaded(object sender, RoutedEventArgs e)
{
timer = new System.Windows.Threading.DispatcherTimer();
timer.Interval = new TimeSpan(0,0,1); //間隔1秒
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
如注釋所說,我把間隔設定為1秒。該計時器的間隔事件也是Tick事件。好了,一切就緒,最後讓我們來看看timer_Tick中的代碼:
void timer_Tick(object sender, EventArgs e)
{
textBlock1.Text = DateTime.Now.ToLongTimeString();
}
似乎和原來的Tick事件中的代碼沒啥兩樣,那就運行一下看看效果吧。。。。
左側為Windows 2003下運行結果,右側為Vista下運行結果(發覺一個細節,ToLongTimeString在不同環境下返回的字串盡然不一樣,有誰知道為啥嗎?)
完工!一個簡單的電子鐘做好了。下面讓我們來講講為什麼我們沒有用我們相對熟悉的System.Timers.Timer。其實在.net 3.0中這個Timer已經與過去我們所瞭解的Timer不同了,如果我們在這個例子中使用該計時器的話,我們將收到一下錯誤資訊:
由於其他線程擁有此對象,因此調用線程無法對其進行訪問。
根據MSDN文檔的說明,System.Timers.Timer是在一個獨立的線程上實現的,而我們要更新的TextBlock位於視窗線程中,所以會出現以上錯誤。所以在國外的大部分的WPF Blog中,作者們比較喜歡使用DispatcherTimer,因為它簡單方便,似乎和過去Timer更相似。至於System.Timers.Timer到底該如何?對UI的更新,我還沒有底,如果有誰已經知道怎麼弄了,請分享一下。
=======================Update on 2006.12.23======================
基於System.Timers.Timer的解決方案(由Tyrael 提供)
private delegate void UpdateTimer();
private void UpdateTimerCallback()
{
textBlock1.Text = DateTime.Now.ToLongTimeString();
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new UpdateTimer(UpdateTimerCallback));
}
這裡的timer_Elapsed事件處理常式對應於Timer的Elapsed事件