我的小闹钟

来源:互联网 发布:过滤照相软件 编辑:程序博客网 时间:2024/04/29 13:41
其实自己很早就想要做一个小闹钟的程序,但因为工作一直都很忙,就没有定下心来。前几天偶尔看到一篇文章是关于时钟的程序,我下载来看了以后突然产生了浓厚的兴趣,正好这两天稍稍空了点,就打算自己整这么一个小东西玩玩。 结果花了大约一周的时间,把这个时钟的大体功能都完成了。在这里把一些心得记录下来,有兴趣的朋友可以把源码下载去研究研究。

最基本的问题——时钟指针

时钟没了指针象什么话啊,如果仅仅用数字来显示的话那就失去开发这么一个程序的意义了。不怕大家笑话,其实最初一直没有动手做的最主要原因是不知道该怎么正确地绘制指针的位置,在学校里学的那些最基本的数学知识N年前就交还给老师了。所以,在The Code Project上看到的这篇文章和他的程序给了我很大的启发。

这篇文章是一位马来西亚的人写的,他的做法打破了我的一直以来的困惑。其实要绘制指针很简单,以前我的想法是根据当前的时间来计算指针的终点再画线,而他的做法正好相反,指针的终点是相对固定的,而改变的是坐标。换一种思维可以这样想,当时间在走动的时候,时针其实是不动的,而是钟的底盘在转。这里用到了System.Drawing.Graphics类的两个方法,TranslateTransform和RotateTransform。

TranslateTransform()的作用是将坐标系进行平移,以改变原点的位置,这样一来,指针起点的位置可以恒为(0, 0),而不需要对每根指针都重新计算起点坐标。
RotateTransform()的作用是将坐标系进行旋转,也就是前面说的钟的底盘在转,那么绘制指针就只要按一个固定的方向就可以了。

通过以上两个方法的配合,钟的三根指针就全部搞定,同时也扫除了我一个比较大障碍。具体的实现代码可参阅源码中MainForm.cs的OnPaint方法。

锦上添花的处理——可穿越式窗体

可能会有很多朋友和我有一样的感觉,好端端的桌面上有这么一个时钟摆着,乍看还挺好,时间长了会非常碍事,因为时钟会把下面的窗体遮盖掉。尽管钟的表面可能是透明的,但如果正好下面一个按钮或链接什么的,被钟遮掉变得能看不能点,还得移动窗口再去点击,使用起来会非常麻烦。所以,我在时钟大体完工以后就开始着手解决这个问题。

灵感的来源是Windows Live Messenger的动漫传情功能,仔细的人都会发现,在播放动漫传情的时候,鼠标没办法点在动画上,而是点到了后面的窗体上,就象鼠标的点击穿越了动画一样。最初的时候我以为是直接把动画绘制在窗体表面,但后来发现所谓的动漫传情其实是个Flash,所以觉得肯定不是用这种方法的。经过大量的的盲目搜索,终于让我找到了实现这种效果的方法。

其实要实现这种可穿越的效果很简单,MS在Windows 2000开始就引入了一种称为分层的窗口(Layered Windows),通过这种窗口可以实现窗口的透明化,比如设置了Form的Opacity属性之后就可以使用整个窗体呈现透明度的效果,这就是通过这种分层窗口实现的。虽然.NET的WinForm里已经考虑了对分层窗口的支持,但没有直接提供这种可穿越的功能,所以需要自己来实现。

有过Windows原生窗体编程经验的人都知道(特别是C++程序员),每个窗体都有一些样式(Style),比如WS_CAPTION, WS_VISIBLE等等,如果要实现这种可穿越的效果,就需要给窗体增加两种样式——WS_EX_LAYERED和WS_EX_TRANSPARENT。注意,这里的WS_EX_TRANSPARENT的意思并不是视觉上的透明,而是程序逻辑上的透明,也就是所有的鼠标消息都不会发送给这个窗口,而是发送给它下面的窗口,这样一来,就自然实现了可穿越的效果。

接下来就是要将这两种样式应用到我们的窗口上,可以用GetWindowLong和SetWindowLong这两个API函数来实现。在调用了SetWindowLong设置了WS_EX_TRANSPARENT样式以后,还要再立刻调用SetLayeredWindowAttributes函数来设置窗口的透明度,否则窗口的透明度是0,我们就看不到了。具体的实现代码可以参阅源码中NativeMethods.cs文件的SetFormTransparent方法。

最头疼的问题——声音的播放

我在这个时钟程序里两处使用到声音,一处是整点报时,另一处是闹铃声音。到目前为止,我已经使用了第三套方案来播放声音了,并且在研究声音播放的问题上我花的功夫最大,因为.NET对媒体的支持弱了一点,而我又不想去折腾复杂而庞大的DirectX SDK。

第一套方案中我用的是System.Media.SoundPlayer进行声音的播放,但很快我就发现这样不行,因为它只能播放WAV格式的音频文件,这会使用户体验大打折扣,所以很快我就否决了这个方案。而且很大的一个缺点是很难抓到声音播放完成的事件,给界面的控制带来了一定的难度。

第二套方案我使用的是Windows Media Player控件,这个东西非常好用,稍稍写了一点代码就可以用了。但在实际使用中我发现,嵌入了这个控件之后,前面的问题都解决了,但程序运行时所占的内存太多,所以最终还是放弃了。

第三套方案也就是现在正在使用的,是利用MCI来播放音频文件。对MCI的支持在.NET里是完全没有提供的,好在MCI提供了MCIWnd这个东西来包装许多MCI指令,这样做起来方便了许多。在这套方案里全部用了API和Windows消息来控制,首先用MCIWndCreate函数来创建一个MCIWnd对象,在需要播放声音的时候向这个MCIWnd分别发送一个MCIWNDM_OPEN和MCI_PLAY消息,在它的播放状态改变时,可以通过接收MCIWNDM_NOTIFYMODE消息获取当前状态并对界面进行相应的处理。

对声音的播放我已经封装在一个控件里了,具体的实现方法可以参阅源码中的MediaButton.cs文件。

下一步的设想

程序目前的版本是1.1,现在的功能基本上是该有的都有了,不过我还是没有觉得非常满意,有一个我非常想实现的功能是换肤。现在程序实现的逻辑上虽然已经有了简单的结构,但是离真正的实时换肤以及用户自定义皮肤差得很远。我打算在1.2版本里加入皮肤功能,同时也希望有兴趣的朋友在阅读了我的代码以后给点建议,毕竟这个领域我没怎么接触过(我一直是做Web开发的)。

本地化功能我目前还没有考虑,这个也会在今后的版本中推出。

另外现在自己也发现了程序存在一些BUG,最明显的整点报时有时会响,有时不会响,但一直没有找到原因,估且放着吧。

程序和源代码我都发布CSDN我的资源里了,欢迎大家下载和拍砖。还有这个闹钟的名字我一直想不好,如果各位有什么好主意不妨说说。


参考资料

  • A Simple Analog Clock Widget (gan.gary)
  • Transparent, Click-Through Forms (Dave Kreskowiak)
 
原创粉丝点击