现在越来越多的软件使用Ribbon Control代替了传统的菜单和工具条,不仅是微软自家的软件Office和Win 10中的程序,市面上其他各种各样的软件也都选择了Ribbon Control。小型的如福昕阅读器、截图软件HyperSnap,大型的如CAD设计软件Creo。
作为WinForm平台开发人员,一直苦于微软不添加此控件,而WPF中早就有此控件了。不想在WinForm中嵌入WPF控件,也不想为了一个控件而应用第三方控件库。所以唯一的方法是自己写一个。
Ribbon Control介绍
我参照MSDN对Ribbon Control进行了分析,发现它是一个很复杂的控件,功能很多。我见过的控件除了DataGridView之外,其复杂度没有任何一个能与之匹敌。它几乎可以承载任何的控件在上面,而且很多都是原来的WinForm不自带的。以下只是部分功能截图:
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类,所有的控件都继承此类来实现自己的功能。
目前这样设计只支持用代码添加控件,不支持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; }
最后效果如图:
点击按钮:
源码下载:
本文章由创风网原创,转载请注明出处:http://www.windite.com/article/details/m4dbxd15