65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows 7 版 Gmail

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (37投票s)

2010年1月17日

CPOL

15分钟阅读

viewsIcon

155402

downloadIcon

8578

一个小型应用程序,使用新的 Windows 7 功能通知您新的 Gmail。

引言

当我开始使用 Windows 7 时,我知道一定有办法利用一些很酷的新功能来帮助我及时了解所有 Gmail 账户的最新信息。但唉,我找不到任何我想要的现有应用程序。所以,我决定编写一个应用程序,让我能同时实现两个愿望。那就是更深入地了解这些新的 Windows 7 功能,并获得一个符合我期望的 Gmail 通知应用程序。这真是一个双赢的提议。

在本文中,我将告诉您如何使用此应用程序所利用的 Windows 7 功能,以及我所学到的经验教训。希望这对您有所帮助。

尽管此应用程序被称为 Gmail for Windows 7, 但它可以在其他 Windows 版本中运行。它通过在执行任何 Windows 7 特定代码之前使用 TaskbarManager.IsPlatformSupported 属性检查 Windows 7 来实现这一点。

这里有一些截图

主屏幕

任务栏通知

自定义缩略图

背景

我首先盘点了可能对这类应用程序有用的一些新的 Windows 7 功能。以下是我初步考虑事项的列表:

  • 跳转列表
  • 应用程序预览缩略图
  • 图标叠加
  • 任务栏图标进度指示器

最后,我决定使用所有这些功能。起初我没有使用进度指示器,因为检查速度很快。但是,我决定为了教学目的,还是值得加入的,即使没有其他原因。

我很快了解到了 适用于 Microsoft® .NET Framework 的 Windows® API 代码包,并决定使用它。

因此,最终这个应用程序使用了以下可能对其他开发者有用的东西:

  • Gmail 的 atom feed
  • 内存中的位图/图标生成
  • Windows® API 代码包
  • 弹出气球通知窗口
  • Windows 7 功能
    • 跳转列表
    • 应用程序预览缩略图
    • 图标叠加
    • 进度指示器

Using the Code

Visual Studio 2008 解决方案包含在 源代码 zip 文件 中。您只需将其解压缩到任何目录,然后打开 GmailForWin7.sln 文件即可。

关注点

此项目中值得一提的有几个方面:

  1. 从 Gmail 检索未读消息
  2. 主窗体
  3. 跳转列表
  4. 缩略图
  5. 气球通知窗口
  6. 设置对话框
  7. 加密
  8. 进度指示器

1. 从 Gmail 检索未读消息

我不喜欢一些 Gmail 通知程序的一点是,它们使用 POP 或 IMAP 从 Gmail 检索消息。这些程序的问题在于,它们在检索消息时会将其移出您的收件箱。我真的只是想随时知道我是否有任何重要邮件。如果有,我就可以轻松启动 Gmail 来处理这些消息。我也想能够从多个位置进行此操作。因此,我想要一种在不影响消息状态的情况下检索未读消息的方法。事实证明,Gmail 有一个 ATOM feed,非常符合我的需求。我对这个 feed 唯一不喜欢的是,您只能获取每封电子邮件的摘要行文本,而不能获取全部内容。我找不到查询全部文本的方法。不过,我真的只需要主题行和消息的第一部分就能知道我是否有任何值得阅读的内容。所以,这已经足够了,我继续使用 ATOM feed。

将这些消息检索到 XmlDocument 中的代码出奇地简单。

    string GmailAtomUrl = @"https://mail.google.com/mail/feed/atom";

    XmlUrlResolver xmlResolver = new XmlUrlResolver();
    xmlResolver.Credentials = new NetworkCredential(account.UserName, account.Password);
    XmlTextReader xmlReader = new XmlTextReader(GmailAtomUrl);
    xmlReader.XmlResolver = xmlResolver;
    try
    {
        XNamespace ns = XNamespace.Get("http://purl.org/atom/ns#");
        XDocument xmlFeed = XDocument.Load(xmlReader);
        var emailItems = from item in xmlFeed.Descendants(ns + "entry")
                         select new
                         {
                             Author = item.Element(ns + "author").Element(ns + 
				"name").Value + "(" + item.Element(ns + 
				"author").Element(ns + "email").Value + ")",
                             Title = item.Element(ns + "title").Value,
                             Link = item.Element(ns + "link").Attribute("href").Value,
                             Summary = item.Element(ns + "summary").Value,
                             Date = item.Element(ns + "issued").Value
                         };

        // Read in the result
        XmlDocument document = new XmlDocument();
        document.Load(xmlReader);

2. 主窗体

主窗体有一个选项卡用于每个已配置的 Gmail 账户。您可以设置最多 5 个账户。这些选项卡将包含该账户 Gmail 收件箱中所有未读消息的列表。

关于这个窗体,最值得关注的是它控制着 JumpList 的创建和更新,处理由 JumpList 项目生成的消息,更新任务栏叠加图标,并控制显示气球通知消息。

3. 跳转列表

Windows API 代码包使处理 JumpList 变得非常容易。我从 JumpList中学到的最大一点是,JumpList 不是由您的应用程序托管的,而是由操作系统本身托管的。因此,应用程序对 JumpList 的控制不如我最初认为的那么大。它实际上只是控制要包含哪些项目。JumpList 项目可以指向 URL、文件位置或应用程序文件名。单击 JumpList 中的项目会导致该项目由 Windows 7 执行。它 **不会** 向您的应用程序发送消息。基本上,单击 JumpList 中的项目与在命令提示符下键入字符串执行的操作相同。

JumpList 中的预定义类别之一是任务类别。此应用程序在此组中添加了两个任务:立即检查和设置。由于单击 JumpList 项目时不会向应用程序发送消息,因此必须设计一种方法来将该事件传达给当前正在运行的实例。与大多数执行类似操作的应用程序一样,我选择启动程序的另一个实例,然后向当前正在运行的实例发送消息,让它知道需要执行某些操作。

WindowsMessageHelper 类在此目的上非常有帮助。它向操作系统注册了两个自定义窗口消息,用于在两个应用程序实例之间进行通信。当单击 JumpList 任务链接时,它会启动该应用程序的另一个实例,并带有两个程序参数之一,以告知它需要执行给定的任务。第二个实例会查找当前正在运行的程序,并根据所需的操作向该实例发送窗口消息。

但是,在我们可以从 JumpList 发送消息之前,必须先创建一个。创建跳转列表非常简单:

    JumpList.CreateJumpList();

这就是我创建任务链接的方式:

    List tasks = new List();
    tasks.Add(new JumpListLink(Assembly.GetEntryAssembly().Location, "Check Now") 
	{ Arguments = "CheckNow" });
    tasks.Add(new JumpListLink(Assembly.GetEntryAssembly().Location, "Settings...") 
	{ Arguments = "Settings" });
    _jumpList.AddUserTasks(tasks.ToArray());

有限数量的项目

我吃过苦头才得知,JumpList 中只能有固定数量的项目。这是一个系统范围的设置,无法更改而不会影响所有 JumpList。您可以使用只读属性 JumpList.MaxSlotsInList 来获取非任务项的最大数量。看起来您最多可以拥有 2 * JumpList.MaxSlotsInList 个总项目,包括任务和非任务类别相关项目,在 JumpList 中。如果您的任务数量超过 JumpList.MaxSlotsInList,那么您的非任务项就会开始失去槽位。

例如,我的 JumpList.MaxSlotsInList 设置为默认值 10。我添加了 11 个任务,它从我的非任务项中占用了 1 个槽位。我添加了 12 个任务,它占用了 2 个用户非任务槽位。

一旦创建了 JumpList,您就可以像这样添加类别:

IconReference iconRef = new IconReference
	(Assembly.GetEntryAssembly().Location, IconBaseIndex + account.IconIndex);
JumpListCustomCategory category = new JumpListCustomCategory(account.AccountName);
categories.Add(category);
_jumpList.AddCustomCategories(categories.ToArray());

我费了很大力气才让那个简单的 IconReference 工作起来。操作系统无法链接到托管应用程序中的图标。所以,我不得不创建一个老式的 RC 文件来引用这些图标。然后,我在项目的属性的应用程序部分中将这些资源链接到应用程序。

创建类别后,您可以像这样添加项目:

List<JumpListLink> listItems = new List<JumpListLink>();
JumpListLink link = new JumpListLink("http://gmail.google.com", "Gmail");
category.AddJumpListItems(listItems.ToArray());

如果您想启动一个应用程序并为其提供参数,您可以使用类似这样的方法:

JumpListLink link = new JumpListLink(Assembly.GetEntryAssembly().Location, "Error") 
	{ Arguments = "Settings" }

要记住的一个重要点是,JumpList 的更改不会显示给用户,直到在 JumpList 上调用 Refresh,如下所示:

_jumpList.Refresh();

4. 缩略图

Windows 7 的另一个很酷的新功能是能够进行标签页预览(称为缩略图),这些缩略图可以从任务栏图标访问。当我注意到我可以在 Internet Explorer 8 中看到我所有打开的标签页,并从任务栏选择我想打开的标签页时,我非常喜欢这个功能。我非常想将其集成到这个应用程序中。所以,我想每个账户都可以成为一个标签页,并拥有自己的预览缩略图。

我首先像大多数应用程序一样添加了这些缩略图,它们捕获标签页/屏幕的屏幕截图用作预览,并显示一个缩小版本作为缩略图。有一个很好的内置方法可以轻松实现这一点,称为 TabbedThumbnailScreenCapture.GrabWindowBitmap。事情进展得很顺利。

但是,我很快遇到了一个问题。我从启动文件夹中以最小化模式启动了这个 Gmail 通知程序。这是一个问题,因为在捕获屏幕截图之前,应用程序必须至少显示一次,而以最小化模式启动不算。所以,唯一的解决办法是使用某种技巧,快速显示窗口,然后再次隐藏它。我不喜欢这样。但是,还有一个更大的原因让我不喜欢缩小屏幕截图的整个方法,那就是它根本没有用。所有标签页看起来都差不多,你无法真正区分它们。所以,我决定深入研究一下这些缩略图背后的原理。

事实证明,这些缩略图只是位图图像。所以,您可以生成任何您想要显示的位图作为缩略图。我决定,如果不打开主屏幕,就能读取新邮件主题将非常有益。所以,我生成了一个位图,它只是一个在位图上绘制的电子邮件列表。其中最难的部分是让缩放效果得以实现,以便文本显示得很好。事实证明,设置这个的代码非常简单:

TabbedThumbnail preview = 
	TaskbarManager.Instance.TabbedThumbnail.GetThumbnailPreview(tab);
using (Bitmap = new Bitmap(300, 300))
{
    /* Some code to draw on the bitmap */
    preview.SetImage(bitmap);
}

更新缩略图

一旦创建,这些缩略图通常会反映它们所代表窗口的当前状态。因此,如果窗口发生更改,其关联的缩略图就需要更新。这是通过替换分配给缩略图的位图来完成的。位图是使用 TabbedThumbnail.SetImage 方法设置的。您有两个选择关于何时更新缩略图图像。您可以监听 TabbedThumbnail.TabbedThumbnailBitmapRequested 事件,并在事件处理程序中调用 SetImage。或者,您可以选择在有意义的时候自行安排更新图像。对于这个应用程序来说,这是一个相当容易的选择,因为缩略图的内容相当静态。缩略图在每次邮件检查后都会更新,我没有监听 TabbedThumbnail.TabbedThumbnailBitmapRequested 事件。

关闭缩略图

如果您注意到,在显示的所有缩略图的左上角都有一个小的 X。这是为了让您无需打开窗口即可从任务栏关闭窗口。这对于大多数应用程序来说很好,但是我们应用程序中的缩略图代表 Gmail 账户,关闭账户是没有意义的。

所以,我开始寻找如何删除或禁用那个小的关闭按钮。我失望地得知没有办法禁用那个按钮。那么,我想,如果我不能禁用它,我就让它在被点击时什么也不做。所以,我监听了 TabbedThumbnail.TabbedThumbnailClosed 事件,并尝试阻止缩略图被移除。但是,在这个事件被调用时,预览对象已经被移除了,所以无能为力。因此,我决定尝试在缩略图被移除之前触发一个可取消的事件。我必须在解决方案中包含 Windows API 代码包的源代码才能做到这一点。然后我修改了 TaskbarWindowManager.DispatchMessage 方法,并在其从窗口列表中移除预览之前触发了一个新事件。该事件运行良好,并且我取消了 TaskbarWindowManager 类进一步的任何操作。然而,那时 JumpList 由操作系统托管的事实又一次让我头疼。C# 代码处理的关于缩略图被点击的消息只是告诉它缩略图已经被关闭了。我无法阻止操作系统关闭缩略图。所以,我撤销了代码包的所有修改,并决定再想一个解决方案。

值得思考的一点:我提到操作系统托管 JumpList,并通过执行命令而不是向您的应用程序发送消息来通知您的应用程序用户在 JumpList 中采取的操作。然而,TabbedThumbnails 能够向您的应用程序发送消息。因此,您的应用程序似乎至少部分负责处理缩略图操作。这似乎是真的,因为 TaskbarWindowManager 会查找并分派与这些缩略图相关的窗口消息。然而,它似乎并不绘制这些缩略图,所以我仍然不确定这发生在何处。

我最终想到的解决方案是,不试图阻止缩略图被关闭,而是让它关闭,然后简单地用相同的信息打开一个新的。是的,这有点笨拙,但我找不到其他方法来实现这一点。如果您连接到与 JumpList 相关的 COM API,可能会有一种方法,但我找到的可行解决方案就是这样做的。

选择缩略图

我提到了一个在缩略图关闭时触发的事件,还有一个事件在缩略图被选中时触发,称为 TabbedThumbnail.TabbedThumbnailActivated。这允许您的应用程序在其缩略图被点击时显示相应的窗口。

5. 气球通知

我希望通过气球弹出窗口收到新邮件通知,类似于 Outlook 或其他应用程序。我在 Windows 7 中找不到任何内置支持。所以,我编写了一个简单的窗口,看起来像我见过的其他气球通知。这个类最有趣的一点是,我使用 CreateRoundRectRgn本机函数创建了一些漂亮的圆角。它在重写的 OnSizeChanged 方法中调用了这个函数。

这个窗口还使用 Graphics.MeasureString 方法来帮助它计算所需的大小,然后将其定位在主显示器的右下角。主显示器的尺寸和位置由 Screen.PrimaryScreen.WorkingArea 属性确定。

6. 设置对话框

这里没有什么特别之处。只是一个用于调整设置和编辑 Gmail 账户的地方。这些对话框中最有趣的部分是在账户设置对话框中,我使用了一个所有者绘制的下拉列表来显示可供选择的图标。我通过监听 ComboBox 控件的 DrawItem 事件来管理这一点。

7. 加密

由于账户信息的一部分是密码,所以我想安全地存储它。我也不想过度地去保护这些信息。我决定使用从当前用户不太可能更改的设置生成的密钥来加密这些密码就足够了。加密和解密密码的代码如下(我省略了 GetKeyBytes 方法,但它会根据计算机和用户设置生成一个密钥)。

private const int KeySize = 24;

private static string EncryptPassword(string password)
{
    byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(password);
    byte[] keyArray = GetKeyBytes(KeySize);

    TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
    tdes.Key = keyArray;
    tdes.Mode = CipherMode.ECB;
    tdes.Padding = PaddingMode.PKCS7;

    ICryptoTransform cTransform = tdes.CreateEncryptor();
    byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 
	0, toEncryptArray.Length);

    return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}

private static string DecryptPassword(string encryptedPassword)
{
    byte[] toEncryptArray = Convert.FromBase64String(encryptedPassword);
    TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
    tdes.Key = GetKeyBytes(KeySize);
    tdes.Mode = CipherMode.ECB;
    tdes.Padding = PaddingMode.PKCS7;

    ICryptoTransform cTransform = tdes.CreateDecryptor();
    byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 
	0, toEncryptArray.Length);

    return UTF8Encoding.UTF8.GetString(resultArray);
}

8. 进度指示器

即使邮件检查非常快速,您也不会长时间看到进度指示器,但我认为将其添加到此应用程序中是为了展示它的易用性。由于我不知道执行邮件检查需要多长时间,所以我使用了指示应用程序正在忙碌的进度指示器版本,而不是显示完成百分比的进度条。要启动进度指示器,请执行以下操作:

    TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Indeterminate);

要将其关闭,只需调用相同的函数,并将 TaskbarProgressBarState 设置为 TaskbarProgressBarState.NoProgress

    TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.NoProgress);

待完成事项

第一件待完成事项

我非常努力地尝试自动化登录不同的 Gmail 账户。我想让您能够从任何账户点击一个电子邮件链接,然后在浏览器中打开该电子邮件。然而,经过许多徒劳无功(和失眠)的几个小时,我终于放弃了。有很多文章讨论如何实现这一点,但谷歌似乎已经停止支持通过 URL 链接执行自动登录的功能。

所以,这意味着如果您点击 在 Gmail 中打开 链接,只有当您碰巧已经登录到该 Gmail 账户时,它才会打开该电子邮件。因此,如果您使用单个 Gmail 账户运行此应用程序,您应该不会遇到任何问题。

第二件待完成事项

设置目前存储在应用程序的 config 文件中,而不是按用户存储。我可能会回来更改这一点,因为它确实困扰我。我确实开始使用 C# 项目的属性设置功能,它会为您生成一个具有给定属性的类。但是,当您将某个内容设置为存储在用户范围内时,它会一直改变您想要加载的 ALL 配置文件。在开发过程中,我不得不一遍又一遍地重新输入我的设置,这非常令人沮丧。

历史

  • 版本 1.0.0.0 - 2010 年 1 月 17 日
  • 版本 1.0.0.1 - 2010 年 1 月 25 日
    • 添加了 打开 Gmail 任务
    • 现在使用进度指示器
© . All rights reserved.