使用全新的 Windows 7 功能创建计时器






4.89/5 (88投票s)
本文介绍如何使用 Windows 7 的新功能创建一个简单的煮蛋计时器。
目录
引言
Microsoft 在 Windows 7 中引入了一些令人惊叹的新功能。我已使用这些功能创建了这个小型应用程序:任务栏、跳转列表、任务对话框和 Aero Glass。
该应用程序只是一个煮蛋计时器:您告诉它需要经过多长时间,它会显示剩余时间。时间到后,您会收到通知
- 通过声音;
- 通过窗口闪烁。
在本文中,您将了解如何在计时器应用程序中使用 Windows 7 功能。
背景
我一直想,为什么标准 Windows 应用程序中没有计时器?我创建了这个应用程序,以便让我在 Windows 7 下的生活和工作更加轻松。
代码
环境
首先,关于环境。开发此应用程序,我使用了
我对 Windows API Code Pack 做了一个小的补丁:请查看Shell/Ext文件夹,其中包含我添加的文件。该补丁允许您创建一个可以在需要时更新其任务栏截图的玻璃窗口。补丁的详细信息将在下一节中介绍。
任务栏
TaskbarTimer 的主窗体只是一个显示时间的 Aero Glass 窗口(我在本文后面进行了描述)。实际上,该应用程序根本不需要它,所有功能都来自任务栏。
这是计时器的任务栏缩略图和预览
每秒,应用程序都会更新缩略图和预览。它还设置任务栏进度
/// Handles timer tick event. Invalidates form image and thumbnails
private void Timer_Tick(object sender, EventArgs e) {
_timeLeft = _timeLeft.Subtract(new TimeSpan(0, 0, 0, 1));
// Invalidate the image
Invalidate();
// Tell the taskbar to invalidate the thumbnail and the preview
InvalidateThumbnails();
// Calculate the percent of completion
int percent = _timeLeft.TotalMilliseconds > 0 ?
(100 - (int)(_timeLeft.TotalMilliseconds / _totalMilliseconds * 100d)) : 100;
if (percent < 0 || percent > 100) {
percent = 0;
}
TaskbarManager.Instance.SetProgressValue(percent, 100);
// ... the rest of code is below
Invalidate
是 Control
的标准方法。在我们的情况下,我们需要调用它,因为窗体可能已打开。在这种情况下,Windows 将重新绘制窗体并更新其上的时间。
InvalidateThumbnails
是 GlassFormWithCustomThumbnails
类的方法,该类是我作为补丁创建的。它非常简单
/// Invalidates the thumbnails
protected void InvalidateThumbnails() {
TabbedThumbnailNativeMethods.DwmInvalidateIconicBitmaps(Handle);
}
我将其放入该类中的唯一原因是 TabbedThumbnailNativeMethods
类被声明为 protected
,因此我们只能从 Shell 程序集使用它。
在此方法之后,Windows 会检查该窗口是否启用了自定义预览。我们在 GlassFormWithCustomThumbnails
类的 OnLoad
方法中启用自定义预览。
/// Enables custom thumbnails for the form
protected override void OnLoad(EventArgs e) {
if (!DesignMode) {
TabbedThumbnailNativeMethods.EnableCustomWindowPreview(Handle, true);
}
base.OnLoad(e);
}
因此,在我们的应用程序中启用了预览。这意味着在调用 InvalidateThumbnails
后,Windows 会执行以下操作。
如您所见,如果任务栏缩略图关闭,Windows 不会执行任何操作。如果已打开,Windows 会将事件发送到窗口。这就是为什么我们必须实现 WndProc
方法。
/// Handles taskbar-related messages
protected override void WndProc(ref System.Windows.Forms.Message m) {
if (m.Msg == (int)TaskbarNativeMethods.WM_DWMSENDICONICTHUMBNAIL) {
int width = (int)((long)m.LParam >> 16);
int height = (int)(((long)m.LParam) & (0xFFFF));
Size requestedSize = new Size(width, height);
using (Bitmap iconicThumb = GetIconicThumbnail(requestedSize)) {
TabbedThumbnailNativeMethods.SetIconicThumbnail(Handle,
iconicThumb.GetHbitmap());
}
} else if (m.Msg == (int)TaskbarNativeMethods.WM_DWMSENDICONICLIVEPREVIEWBITMAP) {
using (Bitmap peekBitmap = GetPeekBitmap()) {
TabbedThumbnailNativeMethods.SetPeekBitmap(Handle,
peekBitmap.GetHbitmap(), false);
}
}
base.WndProc(ref m);
}
这是我们 GlassFormWithCustomThumbnails
类的 WndProc
方法。如您所见,派生类必须处理以下事件。
/// Event to set peek bitmap
protected event GetPeekBitmapDelegate GetPeekBitmap;
/// Event to set the iconic thumbnail
protected event GetIconicThumbnailDelegate GetIconicThumbnail;
我们在 FrmMain
类中实现它们。
/// Constuctor
public FrmMain(int minutes, int elapsedMinutes, TimerOptions options) {
// ... (constructor logic)
GetPeekBitmap += OnGetPeekBitmap;
GetIconicThumbnail += OnGetIconicThumbnail;
}
我们不需要处理这些项返回的位图;它们会在 GlassFormWithCustomThumbnails
类中自动处理。
也许您会问:“为什么您没有全部使用 Windows API Code Pack 来完成?”
首先,我们进行一个小调查。
让我们使用 Visual Studio 查找 TabbedThumbnailNativeMethods.DwmInvalidateIconicBitmaps
方法的所有引用:右键单击该方法,然后单击“查找所有引用”菜单项。我们将看到它仅在一个地方使用(TaskbarWindowManager.cs: 632)。
internal void InvalidatePreview(TaskbarWindow taskbarWindow)
{
if (taskbarWindow != null)
TabbedThumbnailNativeMethods.DwmInvalidateIconicBitmaps(
taskbarWindow.WindowToTellTaskbarAbout);
}
它是 TaskbarWindowManager
类的方法。如果我们查找该方法的所有引用,我们将看到为了使用它,我们必须创建一个 TabbedThumbnail
类的实例。
不幸的是,我们无法获取默认窗口的预览。好的,这是我们可以编写的代码。
- 创建一个新项目并引用 Windows API Code Pack 库:您需要Core和Shell。还要引用PresentationCore和WindowsBase库。
- 在窗体的
Load
事件中编写此代码。
private void Form1_Load(object sender, EventArgs e) {
TabbedThumbnail thumb = new TabbedThumbnail(Handle, Handle);
thumb.DisplayFrameAroundBitmap = false;
TaskbarManager.Instance.TabbedThumbnail.AddThumbnailPreview(thumb);
thumb.SetImage(Bitmap.FromFile(@"d:\temp\vs.png") as Bitmap);
thumb.DisplayFrameAroundBitmap = false;
}
我使用了 Visual Studio 截图作为示例图像。这是结果。
Windows 显示了一个框架,尽管我们已经指定了 DisplayFrameAroundBitmap = false
!而且,没有办法摆脱它。
如果 Microsoft 实现这一点,我将非常高兴。
// this.Handle here is a form for which we want to get the default thumbnail:
TabbedThumbnail thumb =
TaskbarManager.Instance.TabbedThumbnail.GetThumbnailPreview(this.Handle);
不幸的是,此方法返回 null
。这就是为什么我为 Windows API Code Pack 实现补丁的原因。
任务栏按钮
现在,让我们在任务栏缩略图附近添加两个小的暂停和关于按钮。
/// A button in the taskbar to stop the timer
private readonly ThumbnailToolbarButton _buttonPause =
new ThumbnailToolbarButton(Resources.Pause, Resources.PauseTimerTooltip);
/// A button in the taskbar show About dialog
private readonly ThumbnailToolbarButton _buttonAbout =
new ThumbnailToolbarButton(Resources.About,
Resources.AboutTooltip) { DismissOnClick = true };
稍后,在初始化它之后。
_buttonPause.Click += PauseTimer_Clicked;
_buttonAbout.Click += About_Clicked;
ThumbnailToolbarButton[] buttons = new[] {_buttonPause, _buttonAbout};
TaskbarManager.Instance.ThumbnailToolbars.AddButtons(Handle, buttons.ToArray());
稍后,我们可以禁用此按钮(在我们的情况下,当时间到期时,点击该按钮没有任何用处)。
_buttonPause.Enabled = false;
在此调用之后,当您将鼠标悬停在按钮上时,它不会变为蓝色。当然,处理程序也不会被调用。
我们还更改了按钮的图像和工具提示(当用户单击它时,播放图像将变为暂停图像,反之亦然)。
private void PauseTimer_Clicked(object sender, ThumbnailButtonClickedEventArgs e) {
// ... (event logic)
e.ThumbnailButton.Icon = Resources.Play;
e.ThumbnailButton.Tooltip = Resources.StartTimerTooltip;
// ... (event logic continues)
}
如您所见,我们在 ThumbnailButtonClickedEventArgs
中获取了对我们按钮的引用。
请注意 DismissOnClick
属性(我们将其设置为“关于”按钮)。此属性决定单击按钮时任务栏缩略图会发生什么。
- 如果
DismissOnClick
设置为true
,则任务栏缩略图将消失。 - 如果
DismissOnClick
设置为false
,则不会发生任何事情。这是任务栏按钮的默认行为。
在我们的例子中,当用户单击关于按钮时,任务栏缩略图将消失。
Aero Glass 窗口
为了创建 Aero Glass 窗口,我将我的窗体派生自 GlassFormWithCustomThumbnails
。这是我的类,它派生自 GlassWindow
。
我们只需要重写 OnPaint
方法来绘制计时器状态。
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
e.Graphics.DrawString(GetTimeLeftText(), _textFont,
Brushes.Black, ClientRectangle, _stringFormat);
}
如前所述,应用程序的主窗口非常简单,因为它几乎从不使用。但使用 Aero Glass,它看起来不错(Windows 徽标是我的壁纸,它不在窗口上)。
任务对话框
当您启动计时器时,它会显示以下窗口。
这种窗口称为任务对话框。让我们详细检查其各个部分。
在 Windows API Code Pack 的 TaskDialog
类中可以设置所有内容。
/// Constructor. Initlalizes the _dialog
public TimerTaskDialog() {
_dialog = new TaskDialog();
_dialog.Caption = "Taskbar Timer";
_dialog.Cancelable = true;
_dialog.InstructionText = "Please select a time interval for taskbar timer";
_dialog.FooterText = "TaskbarTimer 1.0 Copyright (C) Dmitry Vitkovsky 2009";
_dialog.HyperlinksEnabled = true;
_dialog.StandardButtons = TaskDialogStandardButtons.Close;
TaskDialogCommandLink btn5Munutes = new TaskDialogCommandLink("btn5min",
"5 minutes",
"Set timer to 5 minutes and start the timer");
btn5Munutes.Default = true;
btn5Munutes.Click += (sender, e) => SetInterval(5);
// ... (create other buttons here)
_dialog.Controls.Add(btn5Munutes);
// ... (add other buttons to the dialog here)
}
这是一个处理按钮单击事件的函数。
/// Set the interval to N minutes and close the dialog
/// minutes - N, number of minutes to set
void SetInterval(int minutes) {
_minutes = minutes;
_dialog.Close(TaskDialogResult.CustomButtonClicked);
}
请注意,要在应用程序中使用任务对话框,您必须添加app.manifest文件。您可以在 Windows API Code Pack 示例或本文代码中找到示例。如果缺少清单,您将收到一个异常:“TaskDialog 功能需要加载 comctl32.dll 的版本 6,但当前内存中加载的是不同的版本。”
跳转列表
跳转列表是 Windows 7 的另一个惊人功能。我已在此应用程序中使用它:您可以从列表中选择预定义的时间间隔之一。
此外,还有一个指向上次选择的链接。
所有项都已添加到Tasks部分。这是代码。
/// Configures the jump list public void ConfigureJumpList() { JumpList.AddUserTasks(new[] { GetMinutesLink(5, null, Icon), GetMinutesLink(10, null, Icon), GetMinutesLink(30, null, Icon) }); } /// Adds link "N minutes timer" /// /// minutes - The number of minutes /// timerName - Name of the timer /// icon - The icon for the jump-list private static JumpListLink GetMinutesLink(int minutes, string timerName, IconReference icon) { string title = string.Format("New {0} minute{1} timer", minutes, IsPlural(minutes) ? "s" : ""); string arguments = " -minutes=" + minutes; if (!string.IsNullOrEmpty(timerName) && timerName != TimerOptions.Default.TimerName) { arguments += string.Format(" -name=\"{0}\"", timerName); title += string.Format(" ({0})", timerName); } return new JumpListLink(Application.ExecutablePath, title) { IconReference = icon, Arguments = arguments}; }
请注意:我们不能直接向任务列表添加图标——我们必须传递 IconReference
对象。加载图标有两种选择。
- 从.ico文件
- 从 exe 文件中的资源
这是获取 IconReference
的代码。
/// Gets the icon for the jump-link
private static IconReference Icon {
get {
if (_icon == null) {
string pathToIcon = Path.Combine(
Path.GetDirectoryName(Application.ExecutablePath),
"Timer.ico");
int iconNum = 0;
if (!File.Exists(pathToIcon)) {
pathToIcon = Path.Combine(
KnownFolders.System.Path, "shell32.dll");
iconNum = 20;
}
_icon = new IconReference(pathToIcon, iconNum);
}
return _icon.Value;
}
}
此代码首先尝试查找.ico文件并用它添加 IconReference
。如果磁盘上没有.ico文件,它将使用shell32.dll文件中的图标添加任务项。
请注意我们如何获取shell32.dll文件的路径:我们使用 Windows API Code Pack for Windows 7 中引入的 KnownFolders
类。
此外,我们每次启动计时器时都会添加一个自定义任务。
if (_totalMinutes != 5 && _totalMinutes != 10 && _totalMinutes != 30) {
jumpListHelper.AddCustomLink(_totalMinutes, _options.TimerName);
}
将所有项添加到跳转列表后,我们必须保存它。
/// Saves the jump-list
public void Save() {
JumpList.Refresh();
}
当您右键单击任务栏中的计时器图标时,会显示一个包含上次计时器设置的跳转列表。
跳转列表中的所有项的作用都是使用参数启动计时器应用程序。
<path_to_the_app> -minutes=<number_of_minutes> -name="<name_of_the_timer>"
原生方法
不幸的是,.NET Framework 中并非所有内容都已实现。我已导入这些例程(如果您不熟悉这些函数,可以通过单击以下链接查看其描述)。
您将在 NativeMethods
类中找到它们。
internal static class NativeMethods {
[DllImport("user32.dll", CallingConvention = CallingConvention.Winapi)]
public static extern bool FlashWindow(IntPtr hwnd, bool bInvert);
[DllImport("winmm.dll", SetLastError = true,
CallingConvention = CallingConvention.Winapi)]
public static extern bool PlaySound(string pszSound,
IntPtr hMod, PlaySoundFlags sf);
}
FlashWindow
函数用于通过将窗口图标高亮显示为橙色来通知用户超时。
如果您用鼠标打开任务栏缩略图,窗口将如下图所示。
当计时器到期时,我们只需使用这些方法并移除任务栏进度。
if (_timeLeft.TotalMilliseconds <= 0) {
TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.NoProgress);
timer.Stop();
_buttonPause.Enabled = false;
NativeMethods.FlashWindow(Handle, true);
if (!_options.DisableSound) {
NativeMethods.PlaySound(Path.Combine(KnownFolders.Windows.Path,
"Media\\Windows Default.wav"), IntPtr.Zero,
PlaySoundFlags.SND_FILENAME | PlaySoundFlags.SND_ASYNC);
}
}
如您所见,声音是异步播放的(使用标志 SND_ASYNC
),因此函数会立即返回。
关注点
在本文中,我们学习了
- 如何创建没有框架的可更新缩略图;
- 如果要创建使用所有者绘制的任务栏缩略图的窗口,必须处理哪些事件;
- 如何向任务栏添加按钮;
- 任务对话框的组成部分;
- 如何创建跳转列表。
感谢您的阅读 :)
修订历史
- 2009 年 11 月 - 初次修订。
- 2010 年 1 月 - 添加了对任务栏按钮更详细的描述。