现在越来越多的软件使用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