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

Outlook栏实现

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (122投票s)

2003 年 4 月 13 日

18分钟阅读

viewsIcon

614296

downloadIcon

6241

逐步演示 Outlook 风格图标栏的设计和实现。

目录

引言
最低要求
要求:我需要什么
要求:我不需要什么
原型设计
GUI 组件
对象模型
原型实现
对象
OutlookBar
IconPanel
锦上添花
结论

引言

这是又一个 Outlook 栏实现。虽然 Carlos H. Perez 用 C# 实现了一个出色的版本(参见 https://codeproject.org.cn/cs/miscctrl/OutlookBar.asp),但我想要一个不严重依赖 gdi32user32comctl32 DLL 的实现,并且不深度纠缠于某个人的框架。讽刺吧!因为我需要一个更适合应用程序自动化层(AAL)框架的实现,而 Carlos 的实现虽然非常详尽,但不太适合 AAL 的设想。我想这就是为什么轮子不断被重新发明的原因——因为你需要为不同的工作准备不同的轮子。

我还要做一些在 Code Project 文章中不常看到的事情。大多数文章都是“事后”写的。也就是说,组件或程序已经实现,文章描述了它的设计、用法和有趣的实现细节。在这篇文章中,我将展示开发过程,以便读者看到我在实现控件时所采取的步骤。以这种方式写文章当然很有趣,我希望阅读起来也同样有趣!

最低要求

我将从需求开始。这是我唯一遵循的正式设计过程步骤,即使这样也很麻烦,因为在我的世界里,需求是不断演变的事物。为了限制我在这上面花费的时间,我必须确定我的最低要求。我非常依赖这样一个概念,即我开发的架构将能够随着时间的推移适应额外的需求。

我发现识别需求中我需要什么和不需要什么很有用。“不需要”列表是跟踪未来扩展的好资源,并有助于设计一个良好的架构来支持这些未来需求。不过有一个通用要求——虽然我最终会将控件集成到 AAL 中,但我希望在框架之外开发它,以便其他人可以更容易地修改它并将其集成到他们自己的应用程序中。

要求:我需要什么

  • 控件具有可指定但固定宽度的特性
  • 控件具有可指定但固定高度的特性
  • 控件包含若干“面板”
  • 面板由两部分组成:面板顶部的类似按钮的控件和一组项目
  • 面板中的每个项目都有一个关联的图标
  • 左键单击面板中的项目会触发一个事件
  • 保持在 .NET 环境的范围内。(不使用 DllImport)
  • XML 定义——面板文本、图标位图、事件名称
  • 应最小化所有者绘制的内容,并且在使用清单时控件应看起来美观。

要求:我不需要什么

  • 控件的调整大小以及涉及的问题
  • 大图标和小图标支持
  • 那些可爱的小面板滚动按钮。我还是更喜欢滚动条。
  • 弹出上下文菜单
  • 动画(面板以动画方式“打开”)
  • 一个与窗体设计器兼容的控件。不用,不需要。
  • 面板中除图标以外的其他类型控件。
  • 用户无法重新排序面板(拖动它们以更改顺序)。

一开始就够了。当我开始开发控件时,这个列表可能会改变。(写这个很有意思,因为我还没有开始开发控件,所以我不确定这是否属实,但经验表明它*总是*属实!)

原型设计

GUI 组件

这一步涉及获取一个支持需求的が。由于目标之一是保持在可用工具的 .NET 框架内,实现 Outlook 栏的主要设备将是面板。面板的一个好处是它们为内容提供了自动滚动功能。幸运的是,这种自动滚动功能也可以关闭。我坚信(在我小时候被我的老板/邻居反复灌输)概念应该用文字和图片来传达。通过用文字清晰地传达设计,可以建立一个术语词典,从而促进对设计概念的共同理解,并为确定实现问题奠定基础。

考虑到这一点,我脑海中的基本架构是这样的:

  1. 整个栏将实现为一个禁用自动滚动的面板(我们称之为“栏面板”)
  2. 每个栏将由一个子“面板”组成,其中包含作为按钮表示的栏标题。此面板也将禁用自动滚动。当面板未选中时,面板将调整为按钮的高度,当展开时,它将垂直扩展到父面板的大小。
  3. 一个“图标面板”将用作上述面板的子项。它将被放置在父面板的按钮正下方。此面板*将*启用自动滚动,因为它将包含可能超出整个面板垂直范围的图标。
  4. 从设计角度来看,这个图标面板需要足够抽象,以支持其他类型的面板,例如“树视图面板”或“列表视图面板”(参见 Carlos 的实现)。
  5. 虽然在这个版本中我不会纠结于小图标和大图标支持,但实现一个图像列表是个好主意,仅仅是因为它支持背景颜色定义。

这是我的设想的一张图片:

每个面板的水平和垂直范围仅供说明之用,不一定与实际实现相符。

对象模型

现在 GUI 组件的布局已经开发完成,下一步是设计一个支持此结构的 the 模型。我在处理任何预先存在的类(如这里的按钮和面板)时都有一条经验法则:始终为类提供一个特化(例如,派生类)或一个对象的包装器。虽然我违反这个规则的次数比我承认的要多,但这是一个很好的遵循规则,因为它允许在以后扩展 the 模型。如果直接使用预先存在的类,几乎可以肯定以后在设计过程中会需要一些特化。在实现过程中稍后添加特化可能导致大量类重命名和重新测试。重构,无论何时完成,都是个好主意,但它可能非常昂贵。

那么问题就来了,是实现一个特化类还是包装类。有一些古老的术语仍然有用但不常听到了——“是一种”和“有一个”。它们分别代表特化和包装。在许多情况下,很难弄清楚使用哪一个,就像这里一样。为了帮助做出决定,我经常依赖于对描述问题的语义的分析。在这种情况下,每个对象,即栏面板、面板和图标面板,都是一种特化。如果这些对象派生自 Panel 类,该架构就可以利用(你喜欢这个词吗)Panel 控件集合。这避免了实现一个单独的集合(与 Carlos 的实现形成对比,它有一个面板集合和一个项目集合)。希望这个设计决策不会在以后给我们带来麻烦。我为什么这么说?因为经验告诉我不要过分依赖框架功能。通常会有不希望的副作用。

同样,通过观察 GUI 组件布局和图表的语义,可以清楚地看到“有一个”关系在哪里:

  • 栏面板对象有一个面板。
  • 面板有一个按钮和一个图标面板。
  • 图标面板包含一个图标集合。

一些“有一个”关系是容器形式的,这引发了确定对象 m 对 n 关系的重要问题。

  • 栏面板与面板的关系是 1::n。
  • 面板与按钮的关系是 1::1。
  • 面板与图标面板的关系是 1::1。
  • 图标面板与图标的关系是 1::n。

虽然我几乎不再关心对象模型图了,但我还是把这个画出来了。

我通常在独立开发时不会在设计时指定方法和内部属性,因为这有点浪费时间。作为项目经理,我也不会强制要求我信任的人在设计时进行这些定义。我不信任的(并且外面有很多“资深软件工程师”符合这个标准)程序员需要提出一个完整的对象模型设计和伪代码。我还强制执行命名约定、大小写用法以及方法名的动词-名词顺序。顺便说一句,我认为匈牙利命名法很糟糕。

原型实现

对象

使用上面的图表可以得到以下各种对象的存根。由于使用了 Panel.Controls 属性来包含控件列表,因此在类定义中不会看到容器或集合的实现。
using System;
using System.Windows.Forms;

namespace OutlookBar
{
    public class OutlookBar : Panel
    {
        public OutlookBar()
        {
        }
    }

    internal class BandPanel : Panel
    {
        public BandPanel()
        {
        }
    }

    internal class BandButton : Button
    {
        public BandButton()
        {
        }
    }

    public abstract class ContentPanel : Panel
    {
        public ContentPanel()
        {
        }
    }

    public class IconPanel : ContentPanel
    {
        public IconPanel()
        {
        }
    }

    internal class PanelIcon : PictureBox
    {
        public PanelIcon()
        {
        }
    }
}
请注意,IconPanel 类是公共的。这允许应用程序实例化所需的面板类型(在此实现中,IconPanel 是唯一提供的)。现在让我们开始充实一些细节,从 OutlookBar 类开始。

OutlookBar

首先,需要实例化 OutlookBar 使其显示在窗体上。创建一个新项目,我修改了 Form1 构造函数来实例化 OutlookBar,并指定了一个边框样式,以便可以验证其创建和放置。

public Form1()
{
    //
    // Required for Windows Form Designer support
    //
    InitializeComponent();

    //
    // TODO: Add any constructor code after InitializeComponent call
    //

    OutlookBar outlookBar=new OutlookBar();
    outlookBar.Location=new Point(0, 0);
    outlookBar.Size=new Size(150, this.ClientSize.Height);
    outlookBar.BorderStyle=BorderStyle.FixedSingle;
    Controls.Add(outlookBar);
}
成功!

第一个面板

接下来,必须添加创建面板的方法。这需要指定面板的文本和类型。为了保持简单,应用程序将负责实例化所需的面板类型。这会将实现直接引入一些复杂的尺寸和定位问题,但一如既往,从简单开始是最好的主意。

OutlookBar 类的增强很简单。

public class OutlookBar : Panel
{
    private int buttonHeight;

    public int ButtonHeight
    {
        get
        {
            return buttonHeight;
        }

        set
        {
            buttonHeight=value;
            // do recalc layout for entire bar
        }
    }

    public OutlookBar()
    {
        buttonHeight=25;
    }

    public void AddBand(string caption, ContentPanel content)
    {
        BandPanel bandPanel=new BandPanel(caption, content);
        Controls.Add(bandPanel);
        RecalcLayout(bandPanel);
    }

    private void RecalcLayout(BandPanel bandPanel)
    {
        // the band dimensions
        bandPanel.Location=new Point(0, 0);
        bandPanel.Size=new Size(ClientRectangle.Width, buttonHeight);

        // the contained button dimensions
        bandPanel.Controls[0].Location=new Point(0, 0);
        bandPanel.Controls[0].Size=new Size(ClientRectangle.Width, buttonHeight);

        // the contained content panel dimensions
        bandPanel.Controls[1].Location=new Point(0, buttonHeight);
        bandPanel.Controls[1].Size=new Size(ClientRectangle.Width, 0);
    }
}
结果显示如下:

在此实现中,按钮高度被确定为一个可能需要外部化的参数,以便应用程序控制。显然,当在 Outlook 栏创建后更改按钮高度时,整个栏及其子项都必须重新计算。

面板的 RecalcLayout 方法是确定按钮和关联内容面板尺寸的一个简单起点。此方法已放置在 OutlookBar 类中,因为很快将使用仅包含在 OutlookBar 中的其他信息——即面板的索引。

此实现允许向栏添加一个面板。这是一个不错的起点,因为可以在此时解决激活和停用面板等细节。此外,此实现提出了一些规范中未回答的问题:

  • 所有面板是否都在初始化时一次添加?
  • 可以删除面板吗?
  • 可以在应用程序运行时稍后添加面板吗?
  • 添加面板时是否应指定插入点?

这对于任何规范来说都是典型的。在实现过程中,总会出现规范中未回答的问题,无论它多么深思熟虑。在这个特定原型中,答案分别是“是”、“否”、“否”、“否”。这确实让生活变得更轻松!

多个面板

如果原型首先允许多个面板,那么处理面板的扩展和收缩将更有趣。通过跟踪添加了多少面板,并将面板相对于其他面板的索引传递给 RecalcLayout 方法,可以轻松实现这一点。OutlookBar.Controls.Count 属性提供了必要的信息。因为面板的按钮和内容控件是子控件,所以它们相对于面板的位置,因此只需要修改面板的位置。更改很简单。

public void AddBand(string caption, ContentPanel content)
{
    BandPanel bandPanel=new BandPanel(caption, content);
    Controls.Add(bandPanel);
    RecalcLayout(bandPanel, Controls.Count-1);
}

private void RecalcLayout(BandPanel bandPanel, int index)
{
    // the band dimensions
    bandPanel.Location=new Point(0, buttonHeight*index);
    bandPanel.Size=new Size(ClientRectangle.Width, buttonHeight);

    // the contained button dimensions
    bandPanel.Controls[0].Location=new Point(0, 0);
    bandPanel.Controls[0].Size=new Size(ClientRectangle.Width, buttonHeight);

    // the contained content panel dimensions
    bandPanel.Controls[1].Location=new Point(0, buttonHeight);
    bandPanel.Controls[1].Size=new Size(ClientRectangle.Width, 0);
}
结果是:

激活面板

激活面板需要跟踪活动的面板索引,并在激活面板时重新计算整个栏的布局。首先,这需要处理按钮单击事件。将使用一个事件接收器来处理所有按钮,因此按钮需要包含一些关于自身的信息,以便它可以通知 OutlookBar 它关联的面板应该被选中。此信息保存在按钮实例本身中。(事件是 .NET 框架中的一个绝妙功能,其中一个特点是事件可以与对象实例关联)。各种信息可能需要与面板的按钮关联,但目前索引和 OutlookBar 实例就足够了。一个简单的类可以处理这个问题:

internal class BandTagInfo
{
    public OutlookBar outlookBar;
    public int index;

    public BandTagInfo(OutlookBar ob, int index)
    {
        outlookBar=ob;
        this.index=index;
    }
}
并在 AddBand 方法中进行以下修改:

public void AddBand(string caption, ContentPanel content)
{
    int index=Controls.Count;
    BandTagInfo bti=new BandTagInfo(this, index);
    BandPanel bandPanel=new BandPanel(caption, content, bti);
    Controls.Add(bandPanel);
    RecalcLayout(bandPanel, index);
}

顺便说一句,其余类仍然是初始化各种状态信息的简单存根。

internal class BandPanel : Panel
{
    public BandPanel(string caption, ContentPanel content, BandTagInfo bti)
    {
        BandButton bandButton=new BandButton(caption, bti);
        Controls.Add(bandButton);
        Controls.Add(content);
    }
}

internal class BandButton : Button
{
    private BandTagInfo bti;
    
    public BandButton(string caption, BandTagInfo bti)
    {
        Text=caption;
        FlatStyle=FlatStyle.Standard;
        Visible=true;
        this.bti=bti;
    }
}

public abstract class ContentPanel : Panel
{
    public ContentPanel()
    {
        // initial state
        Visible=false;
    }
}

internal class IconPanel : ContentPanel
{
    public IconPanel()
    {
    }
}

internal class PanelIcon : PictureBox
{
    public PanelIcon()
    {
    }
}
显然,BandTagInfo 对象被传递给 BandButton 构造函数,并由按钮实例保留。现在可以声明事件处理程序并将其与按钮单击关联,然后调用 OutlookBar 类相应实例中的方法来执行实际选择。

internal class BandButton : Button
{
    private BandTagInfo bti;

    public BandButton(string caption, BandTagInfo bti)
    {
        Text=caption;
        FlatStyle=FlatStyle.Standard;
        Visible=true;
        this.bti=bti;
        Click+=new EventHandler(SelectBand);
    }

    private void SelectBand(object sender, EventArgs e)
    {
        bti.outlookBar.SelectBand(bti.index);
    }
}
OutlookBar 类得到增强,以保留当前面板选择并将其初始化为“无选择”。为此属性提供了 getter 和 setter。setter 调用与单击按钮相同的 SelectBand 方法。

public class OutlookBar : Panel
{
    private int buttonHeight;
    private int selectedBand;

    public int SelectedBand
    {
        get
        {
            return selectedBand;
        }
        set
        {
            SelectBand(value);
        }
    }
    ...
SelectBand 方法

public void SelectBand(int index)
{
    selectedBand=index;
    RedrawBands();
}

void RedrawBands()
{
    for (int i=0; i < Controls.Count; i++)
    {
        BandPanel bp=Controls[i] as BandPanel;
        RecalcLayout(bp, i);
    }
}
遍历所有面板控件并指示它们重新计算布局,因此实际工作在该方法中完成。

是时候考虑一些事情了。

  • 当面板被选中时,其高度始终等于栏的高度减去栏上所有按钮的高度。
  • 选定面板之前的面板绘制在栏的顶部。
  • 选定面板之后的面板绘制在选定面板的底部。

这使得有一个未选定的索引“-1”有点烦人,因为所有面板都“在”某个假定的选定面板之后。有一个非常简单的解决方案,你可以在 Outlook 中看到:一个面板总是被选中!因此,默认选择将是索引“0”——第一个面板。每当添加面板时,“选定面板高度”必须重新计算。这实现为一个方法,因为它稍后将作为按钮高度更改和窗口大小更改的一部分使用。

private void UpdateBarInfo()
{
    selectedBandHeight=ClientRectangle.Height-(Controls.Count * buttonHeight);
}

面板布局计算现在必须考虑面板的放置,使用标准:

  • 此面板是否在选定面板之前?
  • 此面板是否为选定面板?
  • 此面板是否在选定面板之后?

private void RecalcLayout(BandPanel bandPanel, int index)
{
    int vPos=(index <= selectedBand) ? buttonHeight*index :
buttonHeight*index+selectedBandHeight; int height=selectedBand==index ? selectedBandHeight : buttonHeight; // the band dimensions bandPanel.Location=new Point(0, vPos); bandPanel.Size=new Size(ClientRectangle.Width, height); // the contained button dimensions bandPanel.Controls[0].Location=new Point(0, 0); bandPanel.Controls[0].Size=new Size(ClientRectangle.Width, buttonHeight); // the contained content panel dimensions bandPanel.Controls[1].Location=new Point(0, buttonHeight); bandPanel.Controls[1].Size=new Size(ClientRectangle.Width, height); }
这导致了一些功能强大的功能,并且基本上完成了面板激活问题。Outlook 栏的按钮现在可以单击,并且它们按要求移动。

一个小细节。AddBand 方法不再能够简单地设置新添加面板的位置和大小,因为这会影响第一个(选定)面板的高度。因此,现在应用程序负责在所有面板创建后选择所需的面板。没有比即时更改规则了,对吧!

应用程序重调大小

此时一个主要问题是:当包含的窗体调整大小时,栏会发生什么?为此,必须添加一个事件处理程序。另一个 .NET 事件机制的绝佳功能是,可以将多个处理程序与同一个事件关联。因此,无论窗体上关联了其他哪些重调大小处理程序,Outlook 栏都可以添加自己的。

public void Initialize()
{
    // parent must exist!
    Parent.SizeChanged+=new EventHandler(SizeChangedEvent);
}
不幸的是,由于这必须在 Outlook 栏添加到窗体(或其他控件)之后完成,因此需要一个单独的初始化方法,以确保父项存在。

事件处理程序本身非常简单。它重新计算 Outlook 栏面板的大小,更新选定面板的高度,并调用 RedrawBands 方法。

private void SizeChangedEvent(object sender, EventArgs e)
{
    Size=new Size(Size.Width, ((Control)sender).ClientRectangle.Size.Height);
    UpdateBarInfo();
    RedrawBands();
}
请注意,此处理程序仅处理垂直重调大小。如果希望处理水平重调大小(例如,当控件包含在拆分窗口中时),则需要单独的机制。

图标面板

该设计一点一点地进行。图标面板应具有以下功能:

  • 图标沿面板的中心线居中放置;
  • 图标带有标题;
  • 单击图标会触发应用程序提供的事件;
  • 图标按照添加到面板的顺序从上到下排列;
  • 图标面板默认为白色背景;
  • 图标需要支持透明度。

首先,将面板的背景颜色设置为白色会暴露父面板的尺寸计算错误。这已得到纠正,以包含按钮高度和内容面板高度。

private void RecalcLayout(BandPanel bandPanel, int index)
{
    int vPos=(index <= selectedBand) ? buttonHeight*index : 
buttonHeight*index+selectedBandHeight; int height=selectedBand==index ? selectedBandHeight+buttonHeight :
buttonHeight; ...
图标面板中的图标由图标图像本身和标题组成。两者都居中。标题必须实现为 IconPanel 包含的 Label。因此,对于图标面板中的每个图标,都会创建两个控件:PanelIcon(派生自 PictureBox)和标题,它们被实例化为 Label。代码最终如下所示:

public class IconPanel : ContentPanel
{
    protected int iconSpacing;
    protected int margin;

    public int IconSpacing
    {
        get
        {
            return iconSpacing;
        }
    }

    public int Margin
    {
        get
        {
            return margin;
        }
    }

    public IconPanel()
    {
        margin=10;
        iconSpacing=32+15+10;    // icon height + text height + margin
        BackColor=Color.White;
        AutoScroll=true;
    }

    public void AddIcon(string caption, Image image, EventHandler onClickEvent)
    {
        int index=Controls.Count/2;    // two entries per icon
        PanelIcon panelIcon=new PanelIcon(this, image, index, onClickEvent);
        Controls.Add(panelIcon);

        Label label=new Label();
        label.Text=caption;
        label.Visible=true;
        label.Location=new Point(0, margin+image.Size.Height+index*iconSpacing);
        label.Size=new Size(Size.Width, 15);
        label.TextAlign=ContentAlignment.TopCenter;
        label.Click+=onClickEvent;
        label.Tag=panelIcon;
        Controls.Add(label);
    }
}

public class PanelIcon : PictureBox
{
    public int index;
    public IconPanel iconPanel;

    public PanelIcon(IconPanel parent, Image image, int index, 
EventHandler onClickEvent) { this.index=index; this.iconPanel=parent; Image=image; Visible=true; Location=new Point(iconPanel.outlookBar.Size.Width/2 -
image.Size.Width/2, iconPanel.Margin + index*iconPanel.IconSpacing); Size=image.Size; Click+=onClickEvent; Tag=this; } }
请注意,PictureBoxLabel 都可以单击。Tag 属性在两种情况下都设置为 PanelIcon,以防应用程序需要有关事件发送方的其他信息。

顺便说一句,将背景更改为其他颜色可以验证图标透明度是否正常工作。

在此图标栏的实现过程中,出现了一些设计问题:

  • 应用程序是否可以指定标题字体?
  • 应用程序是否可以指定标题颜色?
  • 应用程序是否可以更改面板的背景颜色?纹理呢?
  • 更改栏的宽度将需要对文本和图标的居中进行大量重新计算。
  • 没有用于垂直边距和图标间距的 setter 函数。
  • 如果计划支持大图标,则需要调整图标间距。
  • 图标间距是硬编码的,并假定图标大小为 32x32。

这些都是有效问题,但对我目前的需求来说,我不需要考虑它们。(换句话说,我宁愿去海边散步)。

当前的实现结果是一个不错的展示。

出于某种原因,滚动条稍微偏离屏幕右侧且稍微偏长。正确的显示

仅通过修改该内容面板的尺寸来实现

bandPanel.Controls[1].Size=new Size(ClientRectangle.Width-2, height-8);
我对此没有任何解释,因为尺寸是由面板的客户区确定的。如果有人有其他想法,请告诉我!

锦上添花

我想实现一个“锦上添花”的功能,即鼠标进入图标区域时高亮显示图标背景。我的第一个实现很简单,向 PanelIcon 类添加了 MouseEnterMouseLeave 事件处理程序。

private void OnMouseEnter(object sender, EventArgs e)
{
    BackColor=Color.LightCyan;
    BorderStyle=BorderStyle.FixedSingle;
    Location=Location-new Size(1, 1);
}

private void OnMouseLeave(object sender, EventArgs e)
{
    BackColor=bckgColor;
    BorderStyle=BorderStyle.None;
    Location=Location+new Size(1, 1);
}
请注意,在这段代码中,我也设置了边框样式。当我第一次只设置背景时,一切都很好。当我添加打开和关闭图标周围边框的功能时,事情开始出错。首先,图标向右和向下移动了一个像素,这在视觉上不太令人愉悦。其次,如果鼠标从右边缘或底部边缘离开图标,边框将不会消失。为了修复图标的移动,我只需在进入时将其向上和向左移动,在退出时将其向下和向右移回。然而,图标背景仍然没有被正确清除,这只发生在指定边框时。事实证明,问题在于边框,以及一旦调用 OnMouseLeave 事件并且图标重新定位(向前和向下)就会导致调用 OnMouseEnter 事件。随后,不再调用相应的 OnMouseLeave

解决方法是使用 MouseMove 事件确定鼠标位置,并在“进入”图标时使用稍小的尺寸,并设置一个标志来跟踪鼠标是否已被“进入”。以下代码说明了解决方案:

private void OnMouseMove(object sender, MouseEventArgs args)
{
    if ( (args.X < Size.Width-2) &&
        (args.Y < Size.Width-2) &&
        (!mouseEnter) )
    {
        BackColor=Color.LightCyan;
        BorderStyle=BorderStyle.FixedSingle;
        Location=Location-new Size(1, 1);
        mouseEnter=true;
    }
}

private void OnMouseEnter(object sender, EventArgs e)
{
}

private void OnMouseLeave(object sender, EventArgs e)
{
    if (mouseEnter)
    {
        BackColor=bckgColor;
        BorderStyle=BorderStyle.None;
        Location=Location+new Size(1, 1);
        mouseEnter=false;
    }
}
这段代码效果很好,现在当鼠标进入图标时,我有一个漂亮的带边框的高亮图标。

 

结论

什么?就这样?是的,就这样。我现在有了一个漂亮的图标 Outlook 栏,对我来说已经足够了。有很多扩展(和错误检查!)的空间,但那些可以在以后添加。别再瞎胡闹了,是时候在我客户的应用程序中使用这个东西,开始赚取计费时间了。

对于感兴趣的人来说,示例栏是通过在 Form 构造函数中使用几行代码生成的。

OutlookBar outlookBar=new OutlookBar();
outlookBar.Location=new Point(0, 0);
outlookBar.Size=new Size(150, this.ClientSize.Height);
outlookBar.BorderStyle=BorderStyle.FixedSingle;
Controls.Add(outlookBar);
outlookBar.Initialize();

IconPanel iconPanel1=new IconPanel();
IconPanel iconPanel2=new IconPanel();
IconPanel iconPanel3=new IconPanel();
outlookBar.AddBand("Outlook Shortcuts", iconPanel1);
outlookBar.AddBand("My Shortcuts", iconPanel2);
outlookBar.AddBand("Other Shortcuts", iconPanel3);

iconPanel1.AddIcon("Outlook Today", Image.FromFile("img1.ico"), 
new EventHandler(PanelEvent)); iconPanel1.AddIcon("Calendar", Image.FromFile("img2.ico"),
new EventHandler(PanelEvent)); iconPanel1.AddIcon("Contacts", Image.FromFile("img3.ico"),
new EventHandler(PanelEvent)); iconPanel1.AddIcon("Tasks", Image.FromFile("img4.ico"),
new EventHandler(PanelEvent)); outlookBar.SelectBand(0);
尽情享用!
© . All rights reserved.