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

可折叠分割容器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (23投票s)

2014 年 9 月 19 日

CPOL

4分钟阅读

viewsIcon

51376

downloadIcon

4237

适用于 Windows Forms 的可折叠分割容器控件

引言

可折叠的 SplitContainer 控件增强了 Windows Forms 的 SplitContainer,通过在可移动条上提供一个区域,当点击两个按钮之一时,可以折叠相关的面板。您可以自定义按钮区域,选择按钮样式,当选择的样式为 ImagePushButton 时,可以在 Visual Studio 设计器中或在运行时导入用户提供的图像。

当控件加载了向左的图像时,其他三个折叠方向(向右、向上和向下)将自动生成。此外,分隔条宽度会自动调整以适应图像大小。

分隔条按钮有多种视觉样式可供选择,从基本的图像显示到带有图像的功能性按钮。按钮可以位于分隔条的左/上、中心或右/下。按钮样式属性的 ScrollBar 选项使用 Windows Forms 的 ScrollBarRenderer 类在分隔条上生成滚动条按钮。

一些自定义代码添加了功能或纠正了原生 Windows Forms SplitContainer 控件固有的怪癖,例如控制闪烁,纠正原生控件无法使位图格式背景图像透明的能力,以及添加隐藏焦点矩形的功能。

使用控件

类名是 CollapsibleSplitContainer。该控件可作为工具箱项安装,可以将其拖放到您的 Windows 窗体上。或者,可以通过在代码中实例化 CollapsibleSplitContainer 来以编程方式创建控件。

属性

当控件作为工具箱项安装时,可设置的属性在 Visual Studio 设计器中控件的属性窗口的 Collapsible 部分进行了分组。

  • SplitterButtonBitmap - 用于分隔条按钮的位图。通过 LoadImage 方法设置。支持 Bmp、gif、ico 和 png 格式(参见下面的方法描述)。
  • SplitterButtonStyle - 应用于分隔条控件的视觉样式。PushButtonScrollBar 样式支持鼠标悬停时的热点高亮。下图显示了一些按钮图像和样式的示例。
    • 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 使用 DrawImageButtonRendererScrollbarRenderer 来绘制按钮。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。首次发布
© . All rights reserved.