可折叠分割容器






4.83/5 (23投票s)
适用于 Windows Forms 的可折叠分割容器控件
引言
可折叠的 SplitContainer
控件增强了 Windows Forms 的 SplitContainer
,通过在可移动条上提供一个区域,当点击两个按钮之一时,可以折叠相关的面板。您可以自定义按钮区域,选择按钮样式,当选择的样式为 Image
或 PushButton
时,可以在 Visual Studio 设计器中或在运行时导入用户提供的图像。
当控件加载了向左的图像时,其他三个折叠方向(向右、向上和向下)将自动生成。此外,分隔条宽度会自动调整以适应图像大小。
分隔条按钮有多种视觉样式可供选择,从基本的图像显示到带有图像的功能性按钮。按钮可以位于分隔条的左/上、中心或右/下。按钮样式属性的 ScrollBar
选项使用 Windows Forms 的 ScrollBarRenderer
类在分隔条上生成滚动条按钮。
一些自定义代码添加了功能或纠正了原生 Windows Forms SplitContainer
控件固有的怪癖,例如控制闪烁,纠正原生控件无法使位图格式背景图像透明的能力,以及添加隐藏焦点矩形的功能。
使用控件
类名是 CollapsibleSplitContainer
。该控件可作为工具箱项安装,可以将其拖放到您的 Windows 窗体上。或者,可以通过在代码中实例化 CollapsibleSplitContainer
来以编程方式创建控件。
属性
当控件作为工具箱项安装时,可设置的属性在 Visual Studio 设计器中控件的属性窗口的 Collapsible
部分进行了分组。
SplitterButtonBitmap
- 用于分隔条按钮的位图。通过LoadImage
方法设置。支持 Bmp、gif、ico 和 png 格式(参见下面的方法描述)。SplitterButtonStyle
- 应用于分隔条控件的视觉样式。PushButton
和ScrollBar
样式支持鼠标悬停时的热点高亮。下图显示了一些按钮图像和样式的示例。- None - 标准分隔条控件
- Image - 在分隔条上显示用户提供的图像
- PushButton - 使用图像作为背景的功能性按钮
- ScrollBar - 滚动条样式的箭头按钮
SplitterButtonPosition
- 折叠按钮在分隔条上的位置TopLeft
,Center
,BottomRight
SplitterCollapseDistance
- 受影响面板折叠的程度Collapsed
- 只显示一个面板MinSize
- 受影响的面板折叠到分隔条的最小尺寸属性
SplitterFocusHide
- 是否显示或隐藏焦点矩形。基类处理仍然会激活拖动分隔条时显示的半色调网格图案。如果不进行对基类的重大重写,则无法禁用拖动十字线。
LoadImage 方法
显示一个文件打开对话框,以便用户选择用于按钮的图像。支持的图像类型包括位图、png、gif 和图标。初始图像必须是向左的,并将用于创建其他三个方向的箭头。分隔条宽度和按钮大小会自动设置为以适应加载图像的大小。
对于 pushbutton 样式,当图像宽度和高度在 12 到 32 像素之间,并且箭头四周留有 3 像素的间隙以适应按钮边框时,视觉效果最佳。有关示例按钮图像,请参阅演示项目的 Resources 文件夹。
绘制例程
有两个主要绘图函数用于在分隔条上显示按钮。此外,还提供了一个处理绘制按钮周围调整大小的焦点矩形的支持例程。
DrawSplitterBackground
使用背景色填充分隔条背景,并在可用时填充背景图像。
// Fill the splitter background with the background color and image
private void DrawSplitterBackground(Graphics g)
{
Color backcolor = this.BackColor;
if (backcolor == Color.Transparent)
{
// Find the base color that underlies transparency
Control parent = this.Parent;
while (parent.BackColor == Color.Transparent)
{
parent = parent.Parent;
}
backcolor = parent.BackColor;
}
// Paint the background with the underlying background color
using (SolidBrush brush = new SolidBrush(backcolor))
{
g.FillRectangle(brush, this.SplitterRectangle);
}
// Draw the background image if present
if (this.BackgroundImage != null)
{
// Use a texture brush to replicate base class tiling
using (TextureBrush brush = new TextureBrush(this.BackgroundImage))
{
g.FillRectangle(brush, this.SplitterRectangle);
}
}
}
DrawSplitterButtons
根据系统功能和按钮样式渲染分隔条按钮。根据 SplitterButtonStyle
使用 DrawImage
、ButtonRenderer
或 ScrollbarRenderer
来绘制按钮。ScrollBar 按钮状态映射到 Push Button 按钮状态以降低复杂性。
// Render the splitter buttons based on system capability and button style
private void DrawSplitterButtons(Graphics g)
{
if (splitterButtonStyle == ButtonStyle.Image)
{
if (!panel1Minimized)
{
if (splitterVertical) { g.DrawImage(splitterButtonBitmap, rectLeftDown); }
else { g.DrawImage(bitmapUp, rectRightUp); }
}
if (!panel2Minimized)
{
if (splitterVertical) { g.DrawImage(bitmapRight, rectRightUp); }
else { g.DrawImage(bitmapDown, rectLeftDown); }
}
}
else if (splitterButtonStyle == ButtonStyle.PushButton)
{
// Map ScrollBarArrowButtonStates to PushButtonStates
PushButtonState pbs1 = (PushButtonState)((int)button1State & 3);
PushButtonState pbs2 = (PushButtonState)((int)button2State & 3);
if (!panel1Minimized)
{
if (splitterVertical) { ButtonRenderer.DrawButton(g, rectLeftDown, pbs1); }
else { ButtonRenderer.DrawButton(g, rectRightUp, pbs1); }
if (splitterButtonBitmap != null)
{
if (splitterVertical) { g.DrawImage(splitterButtonBitmap, rectLeftDown); }
else { g.DrawImage(bitmapUp, rectRightUp); }
}
}
if (!panel2Minimized)
{
if (splitterVertical) { ButtonRenderer.DrawButton(g, rectRightUp, pbs2); }
else { ButtonRenderer.DrawButton(g, rectLeftDown, pbs2); }
if (splitterButtonBitmap != null)
{
if (splitterVertical) { g.DrawImage(bitmapRight, rectRightUp); }
else { g.DrawImage(bitmapDown, rectLeftDown); }
}
}
}
else if (ScrollBarRenderer.IsSupported && splitterButtonStyle == ButtonStyle.ScrollBar)
{
if (!panel1Minimized)
{
if (splitterVertical) { ScrollBarRenderer.DrawArrowButton(g, rectLeftDown, button1State); }
else{ ScrollBarRenderer.DrawArrowButton(g, rectRightUp, button1State); }
}
if (!panel2Minimized)
{
if (splitterVertical) { ScrollBarRenderer.DrawArrowButton(g, rectRightUp, button2State); }
else { ScrollBarRenderer.DrawArrowButton(g, rectLeftDown, button2State); }
}
}
}
DrawSplitterFocus
重新调整焦点矩形的大小并绘制它,留出空间以容纳分隔条按钮。请注意使用了 ControlPaint
类中的 DrawFocusRectangle
函数。ControlPaint
包含大量用于渲染控件的有用绘图例程。
// Draw the modified focus rectangle if focus is not hidden
private void DrawSplitterFocus(Graphics g)
{
if (splitterButtonStyle == ButtonStyle.None) return;
if (this.Focused && !splitterFocusHide)
{
Rectangle focus = new Rectangle(this.SplitterRectangle.Location, this.SplitterRectangle.Size);
// Draw the focus rectangle to the left/top of the buttons
if (splitterVertical) { focus.Height = rectLeftDown.Top; }
else { focus.Width = rectLeftDown.Left; }
focus.Inflate(-1, -1);
ControlPaint.DrawFocusRectangle(g, focus, this.ForeColor, this.BackColor);
// Draw the focus rectangle to the right/bottom of the buttons
if (splitterVertical)
{
focus.Location = new Point(rectRightUp.Left, rectRightUp.Bottom);
focus.Size = new Size(rectRightUp.Width, this.SplitterRectangle.Bottom - rectRightUp.Bottom);
}
else
{
focus.Location = new Point(rectRightUp.Right + 1, rectRightUp.Top);
focus.Size = new Size(this.SplitterRectangle.Right - rectRightUp.Right - 1,
rectRightUp.Height);
}
focus.Inflate(-1, -1);
ControlPaint.DrawFocusRectangle(g, focus, this.ForeColor, this.BackColor);
}
}
重写的事件处理程序
CollapsibleSplitContainer
中的大部分代码都用于维护工作,例如跟踪按钮方向和按钮悬停/激活状态。重写了几个基类事件处理程序,为控件添加按钮功能。
OnLayout
在影响控件布局的属性更改后强制重绘分隔条。
OnPaint
绘制分隔条,并在启用时绘制按钮。
OnKeyUp, OnMouseMove, etc.
处理键盘和鼠标操作。重写以添加对按钮样式、方向等的测试。
OnBackgroundImageChanged
为背景位图添加图像透明度。基类支持 PNG 和 GIF 的透明度,但不支持位图。
protected override void OnBackgroundImageChanged(EventArgs e)
{
base.OnBackgroundImageChanged(e);
// Add image transparency for bitmap background images. Base class
// supports it for PNG and GIF but not bitmap format
if (this.BackgroundImage != null)
{
((Bitmap)this.BackgroundImage).MakeTransparent();
this.Refresh();
}
}
关注点
SplitContainer
有一些怪癖和 bug。此控件的改编纠正了其中的一些。例如,以下代码有助于减少原生控件的闪烁和调整大小问题
public CollapsibleSplitContainer()
{
// Bug fix for SplitContainer problems with flickering and resizing
ControlStyles cs = ControlStyles.ResizeRedraw | ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer;
this.SetStyle(cs, true);
object[] objArgs = new object[] { cs, true };
MethodInfo objMethodInfo = typeof(Control).GetMethod
("SetStyle", BindingFlags.NonPublic | BindingFlags.Instance);
objMethodInfo.Invoke(this.Panel1, objArgs);
objMethodInfo.Invoke(this.Panel2, objArgs);
}
已知问题:在从垂直方向切换到水平方向然后再切换回来时,分隔条的位置可能与您预期不符,因为宽度和高度尺寸用于计算,并会影响分隔条的位置。
历史
- 2014 年 9 月 19 日:V1.0。首次发布