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

带超链接和淡入淡出的 .NET 系统托盘气泡

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.40/5 (5投票s)

2010年11月16日

CPOL

5分钟阅读

viewsIcon

29227

downloadIcon

1036

.NET C# 系统托盘气泡组件,支持超链接和淡入淡出。

引言

.NET Framework 提供了丰富的用户通知功能,但缺少一个可用于消息驱动型应用程序的系统托盘气泡。本文将为您提供一个现成的组件,并解释如何对其进行自定义和使用。

背景

有时在编写用户交互式应用程序时,尤其是那些处理互联网资源的应用程序,我们需要向用户显示一些信息气泡。消息框或底部的备忘录不是很方便。最方便的通知方式是出现在屏幕右下角的气泡(就像 MSN、Skype 和 ICQ 那样)。

自从我第一次开发这个组件以来,它已经经历了多次修改,以满足客户的所有要求。以下是一些最显著的改进:

  • 它必须是可配置的(背景、声音)
  • 它必须能在扩展显示器上工作
  • 出现时不能抢占输入焦点
  • 不能出现在 Alt-Tab 切换列表中
  • 它必须支持超链接,并在单击链接时打开浏览器
  • 当屏幕上已显示其他气泡时,它必须堆叠显示
  • 它必须支持多线程异步模型,能够处理来自任何线程(而不仅仅是 GUI 线程)的服务
  • 当鼠标悬停在气泡区域时,它必须保持可见

要点

我将在下面解释一些最有趣的地方。

如何让系统托盘气泡出现但不抢占输入焦点

当您在程序中输入文本时,如果一个气泡出现并抢占了输入焦点,那是非常不方便的。如果我们使用常规的 `Form.Show` 方法,就会出现这种糟糕的行为。要显示一个气泡而不改变输入焦点,我们必须使用 `PInvoke ShowWindow` 方法。

[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, 
              int x, int y, int cx, int cy, int flags);

SetWindowPos(Frm.Handle, (IntPtr)(-1), 0, 0, 0, 0, 0x50);

在上面的代码片段中,我调用了原生的 Windows API 函数,将我们的窗口显示在屏幕顶部,高于任何其他窗口。此外,根据最后一个参数,它将以非激活状态显示。这正是我们所需要的。因此,正在输入文本的用户不会被重定向到我们的窗口。

如何解析输入文本并创建超链接

使用 `LinkLabel` 设置多个链接非常简单,并且 MSDN 上有详细说明。但是,我们需要使用输入中的 HTML 文本来做到这一点(我们应该能够从某个地方复制链接并将其粘贴到 `.config` 文件或其他任何地方,然后代码必须进行解析,并将所有 `` 标签转换为链接)。完成此任务的最优方法是使用正则表达式。通常,与基于代码的替代方案相比,Regex 能以更优雅的方式解决基于文本的问题。因此,我们只需使用 Regex 解析文本并剥离所有 `` 标签,然后将它们用作 `LinkLabel` 控件中的链接。

static readonly System.Text.RegularExpressions.Regex A = 
    new System.Text.RegularExpressions.Regex(
            "\\<a\\w+href=\"(?<href>[^\"]*)\"\\W*\\>(?<text>[^\\<]*)\\", 
            System.Text.RegularExpressions.RegexOptions.IgnoreCase | 
            System.Text.RegularExpressions.RegexOptions.Multiline);

        private void SetupText()
        {
            TitleLabel.Text = Title;
            string msg = Message ?? string.Empty;

            var matches = A.Matches(msg);
            if (matches == null || matches.Count == 0)
            {
                MessageLabel.Text = msg;
                MessageLabel.LinkArea = new LinkArea(msg.Length, 0);
            }
            else
            {
                StringBuilder sb = new StringBuilder();
                int last_index = 0;
                foreach (System.Text.RegularExpressions.Match match in matches)
                {
                    var href = match.Groups["href"].Value;
                    var text = match.Groups["text"].Value;

                    sb.Append(msg, last_index, match.Index - last_index);
                    MessageLabel.Links.Add(new LinkLabel.Link(sb.Length,
                        text.Length) { LinkData = href });
                    sb.Append(text);
                    last_index = match.Index + match.Length;
                }
                if (last_index < msg.Length)
                    sb.Append(msg.Substring(last_index));

                MessageLabel.Text = sb.ToString();
            }
        }

工作原理

创建并调整 `TrayBalloon` 对象后,您应该通过调用 `Run` 方法来显示它。此方法将立即返回,因为它旨在供想要通知用户但不与其交互的多线程使用。调用此方法后,您可以继续执行,而不必关心它是如何工作的。

现在,让我们看一下 `TrayBalloon` 经过的关键步骤。首先,它将窗体的显示排队到线程池。之后,线程池线程调用 `TrayBalloon` 窗体的主入口点。在此方法中,`TrayBalloon` 会检查其他可见实例,以便在屏幕上选择一个可用空间(请记住,它必须堆叠显示)。如果没有其他实例,它将在底部显示;如果已显示其他 `TrayBalloon`,它将选择它们之间或上方的可用空间。如果屏幕上没有可用空间,它将覆盖最顶层的 `TrayBalloon` 窗口。另外,在出现时,它会使用动画和淡入淡出效果(但可以根据您显示之前设置的选项跳过)。

`TrayBalloon` 会在屏幕上显示几秒钟,然后开始淡出动画。但是,如果用户将鼠标保留在窗体上方,它将不会隐藏。`TrayBalloon` 隐藏后,它会在可见队列中取消注册自己,以释放屏幕空间,并允许其他 `TrayBalloon` 重用其空间。

这是内部结构的非常简短的描述;请阅读源代码以更详细地了解其工作原理。

结果

这是一个测试应用程序,可用于详细了解 `TrayBalloon.dll` 组件。

tester.JPG

这是最终定制的气泡窗口

trayballoon.JPG

关注点

在测试许多带有系统托盘气泡的应用程序(不仅仅是这个)时,我偶然发现了一个非常有趣的 bug。如果您使 Windows 任务栏可编辑,并在系统托盘气泡出现时将其重新固定到屏幕的另一个角落,Explorer 可能会挂起 :) 请小心。

在使用此组件之前,我们需要设置一些属性,例如 `Title`、`Message` 等。请查看窗体示例,了解必须设置的字段的最低要求。如果您需要为出现的气泡指定声音或自定义背景,请这样做。还有许多其他不同的调整可以应用以获得性能、美观等等。

历史

这是文章的初稿。未来所有新的改进都将发布在这里。

© . All rights reserved.