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

以 Windows 7 的方式浏览 xkcd

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (25投票s)

2010年1月25日

CPOL

5分钟阅读

viewsIcon

51031

downloadIcon

778

一个具有 Windows 7 新功能的桌面应用程序,用于浏览 xkcd。

目录

引言

在本文中,我将介绍一个 Windows 桌面应用程序,该应用程序允许浏览 xkcd 漫画。当您启动该应用程序时,它会显示网站上发布的最新 xkcd 漫画。您可以像在网站上一样浏览其他 xkcd 漫画。它还利用了 Windows 7 的新功能,例如跳转列表、缩略图预览和缩略图工具栏,以提供更好的用户体验。

应用程序的工作原理

解析 xkcd.com

为了从 xkcd.com 显示漫画和相关信息,我们首先需要解析页面。我们需要检索漫画标题、ID、图像位置和鼠标悬停文本。这可以通过使用 HTML Agility Pack 库轻松完成。HTML Agility Pack 还提供了一个名为 HAPExplorer 的应用程序,您可以在其中加载网页的源代码并构建所需的 XPath 表达式。从下面的图像中可以看出,我们需要以下 XPath 表达式

/html[1]/body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]

HAPExplorer

之后,我们需要 h3img 元素来检索必要的信息。以下是相应的代码

public xkcd(string xkcdUrl)
{
    //Get the source of the page
    HtmlWeb loader = new HtmlWeb();
    HtmlDocument doc = loader.Load(xkcdUrl);

    //Extract the part we need
    HtmlNode mainnode = doc.DocumentNode.
      SelectSingleNode("/html[1]/body[1]/div[1]/div[2]/div[1]/div[2]/div[1]/div[1]");
    HtmlNode img = mainnode.SelectSingleNode("img");
    HtmlNode h3 = mainnode.SelectSingleNode("h3");

    //Finally get the details we need
    ImagePath = img.Attributes[0].Value;
    MouseOver = img.Attributes[1].Value;
    Title = img.Attributes[2].Value;

    int temp = 0;
    int.TryParse(h3.InnerText.Replace("Permanent link to this comic: http://xkcd.com",
                                              string.Empty).Replace("/", string.Empty),
                           out temp);
    ID = temp;
    Url = string.Format("http://xkcd.com/{0}/", ID);

    //And the image too
    using (WebClient client = new WebClient())
    {
      using (Stream imagestream = client.OpenRead(ImagePath))
        {
          Image = Image.FromStream(imagestream);
        }
    }
}

与窗体的通信

如上一个源代码片段所示,我们有一个名为 xkcd 的类,该类知道如何获取所需信息。但是,显示它的窗体从不直接实例化该类。相反,所有交互都通过 xkcdService 类进行。该类公开以下方法来获取 xkcd

  • 获取最后一个
  • 获取第一个
  • 获取随机的
  • 获取上一个
  • 获取下一个

由于获取网页的源代码需要一些时间,因此上述所有方法都是异步执行的。当 xkcd 对象可用时,xkcdService 类会触发 xkcdLoaded 事件并传递下载的对象。

此外,xkcdService 类会跟踪当前的 xkcd ID,并公开两个属性,指示是否存在上一个和下一个 xkcd。此外,它还会缓存已加载的对象,以便如果第二次请求该对象,将立即返回。下面的代码片段显示了所有这些是如何完成的

public void GetNext()
{
    //If it is cache return it, otherwise download it.
    if (cache.ContainsKey(currentID + 1))
    {
      OnxkcdLoaded(cache[currentID + 1]);
    }
    else
    {
      worker.RunWorkerAsync(string.Format("http://xkcd.com/{0}/", currentID + 1));
    }
}

private void worker_DoWork(object sender, DoWorkEventArgs e)
{
    string arg = e.Argument as string;

    if (!string.IsNullOrEmpty(arg))
    {
      xkcd temp = new xkcd(arg);

      if (!cache.ContainsKey(temp.ID))
      {
        cache.Add(temp.ID, temp);
      }

      if (arg.Equals("http://xkcd.com/"))
      {
        lastID = temp.ID;
      }

      currentID = temp.ID;
      e.Result = temp;
    }
}

private void OnxkcdLoaded(xkcd result)
{
    currentID = result.ID;

    //Specify if previous and next comic exist
    HasPrevious = result.ID > 1;
    HasNext = result.ID < lastID;

    if (xkcdLoaded != null)
    {
      xkcdLoaded(this, new ExtendedEventArgs<xkcd>(result));
    }
}

窗体订阅了 xkcdLoaded 事件,并显示新漫画。

Windows 7 新功能

任务栏进度条

为了在加载漫画时报告进度,程序使用了任务栏进度条。由于加载漫画的时间未知,因此进度条处于不确定状态。要实现期望的效果,只需一行代码即可

TaskbarManager.Instance.SetProgressState(TaskbarProgressBarState.Indeterminate);

我们得到的结果如下所示

工具栏按钮

该程序还使用工具栏按钮方便漫画之间的导航。工具栏按钮是放置在缩略图预览中的普通按钮。正如我们下面将看到的,创建它们很容易,并且包含三个步骤

//Step 1: Create the buttons.
first = new ThumbnailToolbarButton(Properties.Resources.First, "First");
prev = new ThumbnailToolbarButton(Properties.Resources.Prev, "Previous");
random = new ThumbnailToolbarButton(Properties.Resources.Random, "Random");
next = new ThumbnailToolbarButton(Properties.Resources.Next, "Next");
last = new ThumbnailToolbarButton(Properties.Resources.Last, "Last");

//Step 2: Subscribe to events.
first.Click += new EventHandler<thumbnailbuttonclickedeventargs>(firstButton_Click);
prev.Click += new EventHandler<ThumbnailButtonClickedEventArgs>(prevButton_Click);
random.Click += new EventHandler<ThumbnailButtonClickedEventArgs>(randomButton_Click);
next.Click += new EventHandler<ThumbnailButtonClickedEventArgs>(nextButton_Click);
last.Click += new EventHandler<ThumbnailButtonClickedEventArgs>(lastButton_Click);

//Step 3: Add buttons to toolbar.
TaskbarManager.Instance.ThumbnailToolbars.AddButtons(this.Handle, first, prev, 
                                            random, next, last);

这些按钮的外观如下

缩略图预览

当您将鼠标悬停在任务栏中的窗口上时,会出现缩略图预览。您的应用程序无需一行代码即可获得默认缩略图,但要充分利用它们,您可以根据需要进行自定义。由于应用程序显示漫画,因此我决定最好让缩略图只显示当前漫画而别无他物。为了实现期望的行为,我编写了一个返回对应于当前图像的矩形的扩展方法。之后,将其用作缩略图预览就像以下一样简单

TaskbarManager.Instance.TabbedThumbnail.SetThumbnailClip(
          this.Handle, xkcdPictureBox.GetImageRectangle());

结果是我们得到了当前漫画的缩略图,如上图所示。

跳转列表

顾名思义,跳转列表是一个可以跳转到的任务列表。当您右键单击任务栏中的窗口时,它们就会出现,并提供对常用任务的访问。此程序有两个任务:一个用于访问 http://xkcd.com/about/,另一个用于将当前漫画保存到磁盘。任务在单击时不会公开事件。相反,它们会启动外部应用程序。因此,要创建任务,您需要提供当任务被单击时将启动的应用程序的路径以及任务栏显示的图标。对于第一个任务,应用程序路径需要指向默认浏览器路径。对于第二个任务,我们需要再次启动我们的程序,但我们需要保存第一个程序中的图像。让我们看看我们可以做什么来克服这些问题。

获取默认浏览器路径

事实证明,我们可以使用 AssocQueryString 函数检索默认浏览器路径。根据 MSDN,该函数“从注册表中搜索并检索文件或协议关联相关的字符串”。在 pinvoke.net 的帮助下,我编写了以下方法来获取浏览器路径

private string GetDefaultBrowser()
  {
    uint pcchOut = 0;

    //First get the string length
    NativeMethods.AssocQueryString(NativeMethods.AssocF.Verify,
                NativeMethods.AssocStr.Executable, ".html", 
                null, null, ref pcchOut);
    StringBuilder pszOut = new StringBuilder((int)pcchOut);

    //Then retrieve the path
    NativeMethods.AssocQueryString(NativeMethods.AssocF.Verify, 
                NativeMethods.AssocStr.Executable, ".html", 
                null, pszOut, ref pcchOut);
    return pszOut.ToString();
  }

程序实例之间的通信

由于我们无法从第二个实例访问第一个实例的内存,因此我们需要通知它用户想要保存图像,然后退出。我们可以通过使用 SendMessage 函数发送 Windows 消息来实现。我们发送的消息由程序通过调用 RegisterWindowMessage 函数进行注册。第一个实例通过重写 WndProc 方法来监听消息。因此,当第二个实例启动时,它会检查命令参数,如果找到,则将消息发送到第一个实例。在发送消息之前,它首先需要找到将接收它的窗口的句柄。这可以通过 FindWindow 函数实现。听起来有点复杂,但正如我们所见,它非常简单

static void Main(string[] args)
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    if (args.Length == 0)
    {
      Application.Run(new MainForm());
    }
    else
    {
      //Find first instance window and send the message.
      IntPtr handle = NativeMethods.FindWindow(null, "xkcd Browser");
      NativeMethods.SendMessage(handle, NativeMethods.WM_SAVE, 
                                IntPtr.Zero, IntPtr.Zero);
    }
}

class NativeMethods
{
    public static readonly uint WM_SAVE;

    static NativeMethods()
    {
      WM_SAVE = RegisterWindowMessage("savexkcd");
    }
}

protected override void WndProc(ref Message m)
{
    //If it is the message we need
    if (m.Msg == NativeMethods.WM_SAVE)
    {
      using (CommonSaveFileDialog save = new CommonSaveFileDialog())
      {
        save.DefaultExtension = "png";
        save.DefaultFileName = titleLabel.Text;
        save.AlwaysAppendDefaultExtension = true;
        save.Filters.Add(CommonFileDialogStandardFilters.PictureFiles);

        //Show the SaveFileDialog
        if (save.ShowDialog() == CommonFileDialogResult.OK)
        {
          if (xkcdPictureBox.Image != null)
          {
            xkcdPictureBox.Image.Save(save.FileName, ImageFormat.Png);
          }
        }
      }

      return;
    }

    base.WndProc(ref m);
}

创建跳转列表

我们已经知道如何检索浏览器路径以及如何在不同实例之间进行通信,现在我们可以创建任务了。过程简单直接。正如您所看到的,默认浏览器路径也用于任务图标。

private void InitializeJumpList()
{
    string browser = GetDefaultBrowser();

    //Create jump list
    JumpList jumpList = JumpList.CreateJumpList();

    //Add tasks to the newly created jump list
    jumpList.AddUserTasks(new JumpListLink(browser, "About xkcd")
    {
      IconReference = new IconReference(browser, 0),
        Arguments = "http://xkcd.com/about/"
    });

    jumpList.AddUserTasks(new JumpListLink(
       Application.ExecutablePath,"Save current comic")
    {
      IconReference =new IconReference(
        Path.Combine(Application.StartupPath,"program.ico"), 0),
        Arguments = "save"
    });

    //Finally commit the changes we made.
    jumpList.Refresh();
}

我想是时候看看结果了

最终注释

如果您想知道我为什么构建这个应用程序,答案很简单:它很有趣。这些问题很有挑战性,玩转 Windows 7 API 也很有趣。

欢迎评论、想法、建议和投票。

参考文献

历史

  • 2010 年 1 月 25 日 - 初始发布。
© . All rights reserved.