通过制作一个简单的时钟学习WPF中DispatcherTimer的使用 Level 100

作者: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事件
posted @ 2006-12-21 17:34 Tony Qu 阅读(4415) 评论(9) 编辑 收藏

 回复 引用   
#1楼2006-12-21 19:56 | Tyrael[未注册用户]
使用System.Timers.Timer需要通过Dispatcher来实现UI的更新,代码很简单:

private delegate void UpdateTimer();

private void UpdateTimerCallback()
{
textBlock1.Text = DateTime.Now.ToLongTimeString();
}

void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
//textBlock1.Text = DateTime.Now.ToLongTimeString();
Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new UpdateTimer(UpdateTimerCallback));
}

 回复 引用 查看   
#2楼2006-12-21 20:10 | Jason Cui      
这种开发方式跟一般的WinForm相对有什么好处?
 回复 引用 查看   
#3楼2006-12-21 21:38 | Jeffrey Zhao      
我想ToLongTimeString的返回格式应该和你系统的区域设置相关吧,控制面板里可以调整。
 回复 引用 查看   
#4楼2006-12-21 21:41 | Jeffrey Zhao      
@Jason Cui
从这个例子里其实看不出什么好处,呵呵。

 回复 引用 查看   
#5楼[楼主]2006-12-21 22:50 | Tony Qu      
@Tyrael
谢谢指点

 回复 引用 查看   
#6楼2007-02-24 16:38 | JesseZhao      
有点意思
 回复 引用 查看   
#7楼2007-02-25 11:18 | 冬冬      
@Jason Cui
用XAML描述界面,分离代码,很有些ASP.NET的意思,呵呵。

 回复 引用   
#8楼2007-03-06 13:39 | qu[未注册用户]
你好:
我把程序在Vista系统上运行,时钟也是前一种显示,没有PM的那种,
所以我想,应该与错作系统没有关系。

 回复 引用   
#9楼2007-07-26 10:11 | interim[未注册用户]
弄得不错。学习。