Aero 摇晃
在 .NET WinForms 中实现 Windows 7 Aero Shake 功能。
引言
随着 Microsoft Windows 7 的预发布版本最近出现,Windows 世界也随之涌现了许多新功能。其中一些是简单的想法,可以轻松地集成到现有的 .NET 应用程序中。例如,Beta 版本中的一个便捷功能是能够将窗口停靠在屏幕侧边。在本文中,我们将用 C# 实现另一个新功能:Aero Shake。
Aero 摇晃
显然,要将该功能集成到我们自己的应用程序中,我们必须了解它的作用。Aero Shake 允许用户“摇动”一个窗口(通过鼠标移动它)。一旦窗口检测到摇动,所有其他打开的窗口都会被最小化。如果用户再次摇动窗口,则被最小化的窗口将恢复。
实现概述
将 Aero Shake 功能集成到 WinForms 中分为两部分
- 创建一个 Windows Form,在检测到用户“摇动”时触发一个事件
- 创建一个类来封装最小化和最大化其他应用程序的函数
请注意,代码不会非常复杂,但如果我们想将来利用这些函数,整洁地实现它们将很重要。将功能分解成小代码段的另一个优点是它为开发人员提供了灵活性。改进功能的各个方面和微调代码的行为将更容易。
检测窗口摇动
可以说,最重要的一部分将是检测 Windows Form 何时被摇动。可能存在更复杂的算法,但我选择了一个简单的三部分检查
- 时间。窗体测量从“摇动”开始到结束之间的时间差。这将允许我们过滤掉慢速摇动,因为它们将花费更多时间。
- 距离。同样重要的是测量窗体在整体上移动了多远。这允许我们微调构成“摇动”的“区域”。此外,它有助于过滤掉常规的窗体拖动。
- 数量。此指标确定窗口需要摇动多少次才能算作“摇动”。
一旦获得每个值,我们就会检查每个值是否在某个范围内。请注意,这为“摇动”的定义提供了很大的灵活性。[顺便说一句,这是人们在实际 Windows 7 Aero Shake 中抱怨的事情]。
要测量这些值,我们有两种选择:我们可以使用事件来检测窗体位置何时发生变化,或者我们可以通过 WndProc
检查传递给窗体的消息。对于这个特定的实现,我选择了 WndProc
,因为它更容易检查何时单击标题栏。
时间
获取摇动所需的时间是最容易计算的值。我们只需使用两个 DateTime
字段变量来存储适当的时间间隔。WndProc
可以轻松检测标题栏上的鼠标按下和鼠标抬起操作(源代码显示了所有杂乱的常量)。
请注意,这不是关键的时间测量,因此 DateTime
就足够了。StopWatch
类更精确,但对于我们的目的来说似乎有点矫枉过正。
Distance
距离值稍微复杂一些。我们需要做的是在用户单击窗体标题栏时设置一个标志。然后,每当窗体在屏幕上的位置发生变化时,如果设置了标志,该位置就会存储在点的列表中。当摇动标志首次设置时,点的列表会重置。
一旦用户松开标题栏,我们将拥有一个在“摇动”或执行任何其他移动期间窗体所有位置的列表。我们可以使用点的列表来计算平均位置
private Point GetAveragePoint(List<Point> points)
{
Point avg = new Point();
foreach (Point p in points)
{
avg.X += p.X;
avg.Y += p.Y;
}
avg.X /= points.Count;
avg.Y /= points.Count;
return avg;
}
通过将平均位置与当前窗口位置进行比较,我们可以确定窗口在整体上移动了多远。低值是好的,因为它意味着大部分移动都围绕一个中心点发生,这很好地描述了摇动窗口的外观。
金额
通过简单地检查用于测量距离的列表中存储了多少个点,可以轻松衡量窗口被摇动了多少。
摇动事件
现在我们有了检测摇动的技术细节,剩下要做的就是将其整洁地封装到一个事件中。在我看来,最简单的方法是创建一个新类 ShakeForm
,它继承自常规的 Form
类。
我们的 ShakeForm
类会覆盖 WndProc
来跟踪用户与窗体在屏幕上的位置的交互。每当检测到移动时,我们都会处理遍历的位置列表,看看它们是否描述了摇动。如果它们是,我们将触发我们自己的 FormShaken
事件。
我们如何使用它?任何现有的 Windows Form 都将被修改为继承 ShakeForm
而不是 Form
。然后,将一个方法绑定到 FormShaken
事件,就像任何其他 Form
事件一样。由于 ShakeForm
继承了常规的 Form
类,所有其他 Form
属性和事件都将保持不变。
最小化和最大化
现在,我们有了一种在窗口被摇动时触发代码的干净方法。实现 Aero Shake 的第二部分是相应地最小化和最大化所有其他应用程序。要做到这一点,我们将不得不依赖一些 API 调用。
更改所有打开窗口的窗口状态的常用方法是使用 SendMessage
和 FindWindow
API 调用。例如
IntPtr lHwnd = FindWindow("Shell_TrayWnd", null);
SendMessage(lHwnd, WM_COMMAND, (IntPtr)MIN_ALL, IntPtr.Zero);
然而,问题是上面的代码也会最小化被摇动的窗口。由于我们希望将该窗口保持为唯一未被最小化的窗口,因此我们必须逐一最小化每个打开的窗口。
我们将使用的 API 调用是
GetWindowPlacement
:这将告诉我们其他应用程序的当前窗口状态。ShowWindow
:这将允许我们更改其他应用程序的窗口状态。SetForegroundWindow
:这个只是为了确保当其他窗口恢复时,我们的窗口保持在最前面。
可行的方法是使用 System.Diagnostics.Process
类获取所有当前运行进程的列表。对于每个进程,首先检查它是否有窗口(因为有些进程是后台工作进程),然后检查它是否不是对应于我们当前窗口的进程(因为我们想排除它)。
对于每个有窗口将被最小化的进程,我们将窗口的句柄存储在两个列表之一中:一个用于已最大化的窗口,一个用于正常状态的窗口。存储句柄后,我们使用 ShowWindow
最小化窗口。
稍后,当我们想将最小化的窗口恢复到其先前状态时,我们只需遍历每个列表,然后再次使用 ShowWindow
将它们恢复到正常窗口状态或最大化窗口状态。由于恢复窗口也会将它们移到屏幕顶部,因此最后调用 SetForegroundWindow
使我们可以将摇动的窗体移回前面。为什么我们不使用 this.BringToFront()
?仅仅是因为在此实现中,最大化函数位于一个静态类中。
代码将像 Windows 7 中的 Aero Shake 一样工作,即如果摇动之前有内容被最小化,则它将保持不变。
这种方法的缺点是,运行的应用程序的原始窗口状态得以恢复,但原始堆叠顺序不一定保持不变。
结论
如前所述,代码的各个部分并不复杂。代码的结构对于使实现将来有用至关重要。这也使得改进 Aero Shake 的部分变得容易。例如,你可以选择重写 WndProc
代码以改用 .NET 事件,或者你可能有一个更好的检测摇动的算法,或者你可能只想调整检测的灵敏度。