当前位置:首页 > WinForm

WinForm版Ribbon Control

发表于 2017-06-10 15:35

现在越来越多的软件使用Ribbon Control代替了传统的菜单和工具条,不仅是微软自家的软件Office和Win 10中的程序,市面上其他各种各样的软件也都选择了Ribbon Control。小型的如福昕阅读器、截图软件HyperSnap,大型的如CAD设计软件Creo。

作为WinForm平台开发人员,一直苦于微软不添加此控件,而WPF中早就有此控件了。不想在WinForm中嵌入WPF控件,也不想为了一个控件而应用第三方控件库。所以唯一的方法是自己写一个。


Ribbon Control介绍

我参照MSDN对Ribbon Control进行了分析,发现它是一个很复杂的控件,功能很多。我见过的控件除了DataGridView之外,其复杂度没有任何一个能与之匹敌。它几乎可以承载任何的控件在上面,而且很多都是原来的WinForm不自带的。以下只是部分功能截图:

RibbonStructure.png

Ribbon Control包含Tab,Tab中包含Group,Group中可放入各种各样的控件:Button,Menu,CheckBox等。每一个控件都两种尺寸:Regular和Large,也都可以显示图片和文字。这就涉及到缩放的问题,当整个控件逐渐缩小时,首先尺寸为Large的控件变为Regular,再缩小时,同时显示图片和文字的按钮只显示图片,再缩小时,Group中所有的控件都被放入到一个Menu中。大家可以拿Excel看看效果。

鉴于Ribbon Control是如此的复杂,其实一般很难用到这么全的功能,所以我们并不会完全实现它的功能,而是有选择性的实现。比如Quick Access ToolBar暂时不会实现,我们这是实现常用的,比如:Button,CheckBox,ToggleButton等。

想要了解Ribbon Control的详细组成和使用规范请访问https://msdn.microsoft.com/zh-cn/library/windows/desktop/dn742393(v=vs.85).aspx


架构设计

首先要添加一个自定义控件继承自Control,然后有一个BaseItem类,所有的控件都继承此类来实现自己的功能。

RibbonControlCodeStructure.png

目前这样设计只支持用代码添加控件,不支持Design Time拖控件。


重要实现介绍

1. 此控件不能随意修改大小,所以需要控制高度不能修改,而且Ribbon Control一拖到Form中的时候,大小就是我们想要的。

private const int HEIGHT = 115;

protected override Size DefaultSize
{
    get
    {
        return new Size(base.DefaultSize.Width,HEIGHT);
    }
}

protected override Size DefaultMinimumSize
{
    get
    {
        return new Size( base.DefaultMinimumSize.Width,HEIGHT);
    }
}

2. Ribbon Control的绘制部分时放在RibbonControlPaint.cs文件中。主要时画Application Button和Tab的表头部分,然后调用tab.DrawControl()去实现它下一级控件的绘制部分。

public static void Paint(RibbonControl ribbonControl,Graphics g,bool mouseLeftButtonDown)
{
    Point mousePosition = ribbonControl.PointToClient(Cursor.Position);
    RibbonControlTab tab = null;
    int headerX = 0;
    int headerWidth = 0;
    bool isTabSelected = false;

    if (ribbonControl.ShowApplicationButton)
    {
        headerWidth = ribbonControl.ApplicationButton.GetTabWidth();
        ribbonControl.ApplicationButton.DrawControl(g, new Rectangle(headerX, 0, headerWidth, RibbonControlApplicationButton.BUTTON_HEIGHT), ribbonControl.MetroColorTable, mousePosition);
        headerX += headerWidth + TAB_SPACE;
    }
    else
    {
        headerX += TAB_SPACE;
    }

    for (int i = 0; i < ribbonControl.Tabs.Count; i++)
    {
        tab = ribbonControl.Tabs[i];
        isTabSelected = ribbonControl.SelectedTabIndex == i;

        headerWidth = tab.GetTabWidth();
        tab.DrawControl(g, new Rectangle(headerX, 0, headerWidth, RibbonControlTab.HEADER_HEIGHT), 
            ribbonControl.MetroColorTable, mousePosition, isTabSelected,
            ribbonControl,mouseLeftButtonDown);

        headerX += headerWidth + TAB_SPACE;
    }
}

3. 绘制Group,这个时比较难的地方,因为先要算好Group中每个控件的尺寸,以及他们的布局,然后才能确定Group的尺寸。目前我们只考虑了Button控件。

public void LayoutItems(RibbonControl ribbonControl,int left)
{
    RibbonControlBaseItem item = null;
    int top = TOP + 2;
    int maxTop = TOP + BODY_HEIGHT;
    int maxWidth = 0;
    int leftOriginal = left;

    for (int i = 0; i < this.Items.Count; i++)
    {
        item = this.Items[i];

        if (item is RibbonControlButton)
        {
            RibbonControlButton button = item as RibbonControlButton;

            button.DisplayItemSize = button.ItemSize;
            button.DisplayShowImage = button.ShowImage;
            button.DisplayShowLabel = button.ShowLabel;

            button.CalcSize();

            if(top + button.Size.Height  maxWidth)
                {
                    maxWidth = button.Size.Width;
                }
            }
            else 
            {
                left += maxWidth;
                maxWidth = 0;
                top = TOP + 2;

                //button.Bounds = new Rectangle(left,top,button.Size.Width,button.Size.Height);
                button.Offset(left, top);

                top += button.Size.Height;
                top += 3;

                if (button.Size.Width > maxWidth)
                {
                    maxWidth = button.Size.Width;
                }
            }
        }
    }

    left += maxWidth+3;

    this.Width = left - leftOriginal;

    if(this.Label != "")
    {
        int widthLabel = RibbonControl.CalcTextWidth(this.Label, this.Font);
        if (this.Width < widthLabel)
        {
            this.Width = widthLabel + 4;
        }
    }

    if (this.Width < MIN_SIZE.Width)
    {
        this.Width = MIN_SIZE.Width;
    }

    if (this.Width > left - leftOriginal)
    {
        int increaseWidth = this.Width - (left - leftOriginal);
        int maxLeft = 0;

        for (int i = 0; i < this.Items.Count; i++)
        {
            item = this.Items[i];

            if (item is RibbonControlButton)
            {
                RibbonControlButton button = item as RibbonControlButton;

                if (button.Bounds.Left > maxLeft)
                {
                    maxLeft = button.Bounds.Left;
                }
            }
        }

        for (int i = 0; i < this.Items.Count; i++)
        {
            item = this.Items[i];

            if (item is RibbonControlButton)
            {
                RibbonControlButton button = item as RibbonControlButton;

                if (button.Bounds.Left == maxLeft)
                {
                    button.IncreaseWidth(increaseWidth);
                }
            }
        }
    }
}

4. 实现当用户点击按钮时,相应Click事件。先定义好按钮的事件。

public event EventHandler Click;

public void RaiseClick(EventArgs e)
{
    this.OnClick(e);
}

protected virtual void OnClick(EventArgs e)
{
    if (this.Click != null)
    {
        this.Click(this, e);
    }
}

然后当点击RibbonControl时,获取鼠标位置的按钮,调用按钮的RaiseClick()即可。

protected override void OnMouseClick(MouseEventArgs e)
{
    base.OnMouseClick(e);

    RibbonControlBaseItem controlAtPoint = this.GetControlAtPoint(e.Location);

    if (controlAtPoint != null)
    {
        if (controlAtPoint is RibbonControlTab)
        {
            if (this.Tabs[this.SelectedTabIndex] != controlAtPoint)
            {
                // Select tab
                int tabIndex = this.GetTabIndex((RibbonControlTab)controlAtPoint);
                if (this.SelectedTabIndex != tabIndex)
                {
                    this.SelectTab(tabIndex);
                }
            }
        }
        else if (controlAtPoint is RibbonControlButton)
        {
            RibbonControlButton button = controlAtPoint as RibbonControlButton;

            button.RaiseClick(EventArgs.Empty);
        }
    }

            
}

获取鼠标当前位置的控件代码:

public RibbonControlBaseItem GetControlAtPoint(Point mousePosition)
{
    if (this.ShowApplicationButton)
    {
        if (this.ApplicationButton.IsControlAtPoint(mousePosition))
        {
            return this.ApplicationButton;
        }
    }

    for (int i = 0; i < this.Tabs.Count; i++)
    {
        if (this.Tabs[i].IsControlAtPoint(mousePosition))
        {
            return this.Tabs[i];
        }
    }

    // Item in selected tab.
    if (this.SelectedTabIndex != -1)
    {
        RibbonControlTab selectedTab = this.Tabs[this.SelectedTabIndex];
        RibbonControlGroup group = null;
        RibbonControlBaseItem item = null;

        for (int i = 0; i < selectedTab.Groups.Count; i++)
        {
            group = selectedTab.Groups[i];

            for (int itemCounter = 0; itemCounter < group.Items.Count; itemCounter++)
            {
                item = group.Items[itemCounter];

                if (item is RibbonControlButton)
                {
                    RibbonControlButton button = item as RibbonControlButton;

                    if (button.IsControlAtPoint(mousePosition))
                    {
                        return item;
                    }
                }
            }
        }
    }

    return null;
}


最后效果如图:

tab_insert_demo1.PNG

点击按钮:

tab_home_demo_click1.PNG


源码下载:

Windite.RibbonControl20170610



本文章由创风网原创,转载请注明出处:http://www.windite.com/article/details/m4dbxd15