在日常工作中,经常需要回顾过去几个小时内做了哪些工作,以便创建当天的时间记录条目。Windows桌面上运行的各种应用程序的窗口标题往往能提供很好的提示和提醒,告诉之前在做什么。本文将介绍一个控制台应用程序,它使用几个PInvoke调用来定期记录当前活动窗口的信息到控制台。
在时间驾驶舱(time cockpit)中,有一个应用程序可以追踪来自各种来源的提示(称之为信号),例如更改的文件、可用的无线网络、活动窗口、用户输入等。当需要提醒时,应用程序会在日历中展示这些信息。当然,所有这些信息都是加密的,以确保用户的隐私。活动窗口标题追踪器在大多数时候都能救于水火,因此认为应该分享这段代码以及一个示例应用程序,以防想要做一些类似的事情。
示例中的大部分代码都涉及到使用GetForegroundWindow来检索当前前景窗口及其关联的进程。使用PInvoke,可以很容易地调用这个Win32方法,如下所示(其他函数将在下面解释):
private class NativeMethods
{
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
public static extern int GetWindowThreadProcessId(IntPtr windowHandle, out int processId);
[DllImport("user32.dll")]
public static extern int GetWindowText(int hWnd, StringBuilder text, int count);
}
这些属性看起来可能很神秘,但通过浏览pinvoke.net网站,可以很容易地找到想要调用的API。
WriteCurrentWindowInformation函数首先检索当前窗口标题的句柄,并且只有在活动ID不是空句柄时才会继续:
var activeWindowId = NativeMethods.GetForegroundWindow();
if (activeWindowId.Equals(0))
{
return;
}
由于在某些情况下窗口标题可能是空的(特别是一些Java进程),然后使用GetWindowThreadProcessId来获取窗口句柄所属的进程ID。也检查这个值是否为0,以确保句柄有效:
int processId;
NativeMethods.GetWindowThreadProcessId(activeWindowId, out processId);
if (processId == 0)
{
return;
}
使用进程ID,可以使用Process.GetProcessById函数来检索一个Process结构。这个结构揭示了诸如进程名称、可执行文件中的文件描述或窗口所属的产品名称等信息。
在代码示例中,有多个try / catch块包裹着对进程信息的访问,以防止安全异常,因为管理员进程可能不允许检索此类信息。最后两个块检索窗口标题:如果进程的MainWindowTitle为空,使用GetWindowText方法通过传入主窗口的窗口句柄来检索窗口标题(这里省略了周围的try / catch块以简洁起见):
if (!string.IsNullOrEmpty(foregroundProcess.MainWindowTitle))
{
windowTitle = foregroundProcess.MainWindowTitle;
}
if (string.IsNullOrEmpty(windowTitle))
{
const int Count = 1024;
var sb = new StringBuilder(Count);
NativeMethods.GetWindowText((int)activeWindowId, sb, Count);
windowTitle = sb.ToString();
}
发现对于某些进程,MainWindowTitle有时仍然为空。值得一提的是使用StringBuilder作为原生调用返回值的缓冲区。关于PInvoke参数封送的详细讨论可以在这篇文章中找到。
最后,示例的主循环使用一个简单的Thread.Sleep()调用来定期轮询WriteCurrentWindowInformation函数。
Thread.Sleep(1000); // 1000毫秒
示例的轮询性质可能会让一些人感到不安,同意,如果当前前景窗口发生变化时能够收到通知会更好。这种方法已经在其他地方展示过,但这样做有多个缺点:如果应用程序更改了窗口标题,就不会发生通知。这种情况经常发生,比如当文件被保存,或者例如在Visual Studio中打开不同的解决方案时。
另一个有趣的点是,提供的代码还为Windows 8中引入的Windows Store应用提供了有意义的标题。虽然窗口/应用程序标题非常有用,但在时间驾驶舱中,正在探索使用UI Automation来找到更具体的标题,因为这将允许检查完整的控件树,从而有可能检索当前聚焦文档的标签标题。