转自http://www.cnblogs.com/cxwx/archive/2011/01/11/1932620.html
补充说明,尤其一开始自己没注意到 “程序员之窗”网站提供了示例下载,所以可能我文中有些言语不太妥当,

在此说明,希望不要引起不必要的误会,此文仅当一个学习的例子。


前几天路过一个网站时看到一个关于SplitContainer控件美化的文章,实现的效果如图:(直接引用自那个站)

说起这个站看到的这篇文章,我还真是生气。本来觉得这个效果蛮好的,也想学习下。结果这玩意TMD整个一篇废文,

地址在这里

为什么我要生气呢,这边文章只是展示了最终效果,并且贴了一点点无关紧要的代码,连如何实现,什么原理完全木有讲。给我等菜鸟看来真是一头雾水不说,还要误导大家。

不知道这种文章发出来是为了打广告呢,还是怎么的。

既然人家效果好,那咱就自己动手来实现吧,当然咱不能跟他一样,咱得把具体实现方法过程记录下来供不会的朋友学习。

好了,言归正传! 我们按照上面的图示来看,需要实现的就是SplitContainer 中间那有散点加小三角的那部分,

通过点击小三角就能直接隐藏一个Panel,。

我们一步一步来分析下,如何实现这种效果:

我们先创建一个类 SplitContainerEx 让它继承自SplitContainer,我们的目的是扩展,不是要完全自己做一个SplitContainer。

首先:要实现那种散点加小三角的按钮样式, 我们能想到的就是这里是一个按钮,按钮上面图片或者内容能表示为这种形式;

    不过我觉得按钮的话,存在按钮样式,可能会使美观上看上去不太平滑,所以我想应该是直接在SplitContainer 控件背景上绘制那些散点和小三角第一步知道需要绘制,这也是做自定义控件首先可能会注意到的问题,实际上也就是重写OnPaint 函数了,需要一点GDI知识。 怎么来绘制这一部分呢,如果单纯绘制散点再绘制小三角我觉得是不是有点麻烦,那我们能不能先自己生成一张图片,图片就是散点+小三角,然后再将这张图片绘制到具体           位置呢。 OK,这么想到了,那就没问题了。因为对于我们菜鸟来说,想到是第一步,动手做才是第2步。那我们就来实现绘制一张包括散点加小三角的Bitmap吧:private Bitmap CreateCollapseImage(bool collapse, Color color) {Bitmap bmp = new Bitmap(80, 9);for (int i = 5; i <= 30; i += 5){for (int j = 1; j <= 8; j += 3){bmp.SetPixel(i, j, color);}}for (int i = 50; i <= 75; i += 5){for (int j = 1; j <= 8; j += 3){bmp.SetPixel(i, j, color);}}int p = 35, q = 45;if (collapse){int j = 1;for (int i = p; i <= q; i++){if (j > 8)break;bmp.SetPixel(i, j, color);if (i == q){p++;q--;j++;i = p - 1;}}}else{int j = 8;for (int i = p; i <= q; i++){if (j < 1)break;bmp.SetPixel(i, j, color);if (i == q){p++;q--;j--;i = p - 1;}}}return bmp;}我们首先创建一个 80*9大小的图片,通过SetPixel设置图片中点的颜色,那我们就分析下,高度为9的话,我们可以每隔3个像素竖直方向绘制一个点即(x,2),(x,5),(x,8) 这样正好在竖直方向3个点并且看起来也位置适中比较美观横向的是同一个道理,宽度80,那我就让2端的散点各位6个每隔间隔5像素这种的话左边就是(5,y) (10,y) (15,y) (20,y)(25,y)(30,y)  再加上竖直方向的绘制,正好形成这样一种 6*3 矩阵散点外观效果,还是比较好看滴。 右端同样道理的绘制;好了,散点的绘制没问题了,中间小三角的绘制的话,一下就让我想起来大一学C时,老师总会要求你在CMD窗口中编写算法输出倒三角,菱形==。不过,,,可怜的是我不是个好学生,算法还真不怎么样。所以就有了这么一段绘制代码

绘制小三角算法

  我们散点的绘制左端是到30像素右端散点开始绘制从50像素,那我们小三角的底边宽度就从35到45。然后我们这个时候要考虑到一个问题就是,我们绘制出来的这张图片,要根据Panel面板 收缩或者展开小三角的方向应该是不一样的,所以绘制的时候就做一个判断能形成上下两种方向的小三角。那完整的绘制呢,就是上面给出的 CreateCollapseImage 函数咯。

接着下一步,我们已经能制作出一个散点小三角样式的按钮了,那我们要绘制到哪里呢,。

 太傻了我,当然要绘制到SplitContainer中间伸缩条的正中位置咯,其实SplitContainer控件的一些属性也已经给出可以定位的信息了,我们来看下完整代码:protected override void OnPaint(PaintEventArgs e){//需要绘制的图片Bitmap bmp = CreateCollapseImage(!IsCollpase, CollpaseColor);//绘制区域if (this.Orientation == Orientation.Vertical){rect.X = this.SplitterDistance;rect.Y = this.Height <= 80 ? 0 : this.Height / 2 - 40;rect.Width = 9;rect.Height = 80;bmp.RotateFlip(RotateFlipType.Rotate90FlipX);}else{rect.X = this.Width <= 80 ? 0 : this.Width / 2 - 40;rect.Y = this.SplitterDistance;rect.Width = 80;rect.Height = 9;}//清除绘制区域e.Graphics.SetClip(rect);e.Graphics.Clear(this.BackColor);//绘制e.Graphics.DrawImage(bmp, rect);base.OnPaint(e);}在这里呢,我们其实是需要判断SplitContainer 的分割方向是水平还是竖直,因为这关系到我们绘制这个收缩图片的位置。还有需要注意容错的就是,如果SplitContainer的高度或者说宽度小于需要绘制的图片时,要注意定位好绘制的区域哦。 大家还要注意的是,可能水平和竖直的时候,散点小三角样式中小三角是水平还是竖直方向这个问题,其实这个我们巧取下就可以了,直接生成为水平方向的,如果需要竖直就用 Bitmap.RotateFlip(RotateFlipType.Rotate90FlipX); 方法直接旋转90度就OK咯。

好了,有了前面的两步之后,我们先来处理下细节方面的问题,在考虑如何实现收缩。

 我们知道,我们要能够实现这种散点小三角样式,我们需要的SplitContainer的 SplitterWidth 必须得为9,为什么呢,因为我们需要这么宽或者高的距离来显示这个按钮样式那就需要我们禁止我们开发时能修改SplitterWidth ,所以我们需要覆盖一下基类的SplitterWidth属性/// <summary>/// 隐藏掉基类SplitterWidth属性,使其宽度只能为9/// </summary>public new int SplitterWidth{get{return base.SplitterWidth;}set{base.SplitterWidth = 9;}}还有,我们要想想,如果我实现了这种点击收缩按钮面板就能收缩的扩展之后,我还需要让SplitContainer能够有拆分器吗?我是觉得没必要了,不知道大家如何考虑。所以我需要把IsSplitterFixed 属性也给隐藏掉。还有一点比较关键,直接影响我们这个扩展的美观程度,那就是我鼠标移动到散点小三角样式的按钮上时,它是否应该有所表示或者说变化,比如颜色变化鼠标手势变化=当然,这是必须的,所以由于为了使颜色变化我们还需要其他一些处理,会再下面讲到。

好了,简单说了几个细节,我们继续来讲主要的功能实现,不知道大家熟悉不熟悉SplitContainer这个控件的使用,其实它有很多不错的功能的,一些属性也可能是我们平时不注意的。比如 Panel2MinSize ,Panel2Collpase ,等。

大家可以先设置下这2个属性不同的值,看看效果如何,然后就接着进行我们的设计。这里还要提一点就是,我觉得如果我们实现这样收缩面板的功能,是否要考虑收缩的面板是Panel1还是Panel2呢,这里从我自己的需求,以及简单来说,我就单一实现我们只隐藏和展开Panel2,因为常理中我总觉得2是比较小的,在使用中做拆分的时候需要隐藏较小的那一面。至于我们要怎么实现Panel2的隐藏,其实是非常简单的,先看代码:protected override void OnMouseClick(MouseEventArgs e){if (rect.Contains(e.Location)){if(IsCollpase){IsCollpase = false;this.SplitterDistance = _HeightOrWidth;}else{IsCollpase = true;_HeightOrWidth = this.SplitterDistance;if (this.Orientation == Orientation.Vertical){this.SplitterDistance = this.Width - 9;}else{this.SplitterDistance = this.Height - 9;}}this.Invalidate(this.SplitterRectangle); //局部刷新绘制}base.OnMouseClick(e);}这是必须的,也就是我们在重写了OnPaint后,还需要重写的有OnMouseClick ,OnMouseMove ,这些东西在我们做自定义控件的时候是经常需要处理的!我们这里是在 OnMouseClick 中判断点击,这里有一句if (rect.Contains(e.Location))   大家可以对照OnPait函数里可以发现rect是一个全局的变量,当然这个变量表示的区域就是我们的那个散点三角样式按钮的绘制区域,我们判断鼠标点击在这个区域时才进行处理:处理的过程我必须要说下,其实也是比较巧取的,首先我们需要让需要隐藏的Panel2MinSize最小值为0 ,为什么要这样,大家可以看看我前面说的自己去设置看看效果。Panel2MinSize 为0之后,我们在设置SplitterDistance 拆分器距离左边或者上边的像素距离,就能够很好的隐藏Panel2 并且让拆分器仍然显示,最为巧妙的是 SplitterDistance  属性微软已经帮我们做的很好了,不管水平方向还是竖直方向都是用这同一个属性来表示的,所以我们的处理就非常简单了,不要忘记保存下未收缩前的SplitterDistance 值哦(_HeightOrWidth), 然后就是我们操作的时候为了让样式随着操作同时变化就需要重绘某些部分了,比如this.Invalidate(this.SplitterRectangle); //局部刷新绘制 上面这段说明了,点击收缩之后的操作,还有一步就是鼠标移动到收缩按钮上之后样式的变化,代码如下:protected override void OnMouseMove(MouseEventArgs e){if (this.SplitterRectangle.Contains(e.Location)){if (rect.Contains(e.Location)){this.Cursor = Cursors.Hand;CollpaseColor = SystemColors.GradientActiveCaption;}else{this.Cursor = Cursors.Default;CollpaseColor = SystemColors.ButtonShadow;}this.Invalidate(this.SplitterRectangle);   //局部刷新绘制}base.OnMouseMove(e);}其实这些也都是做自定义控件中常用到的需要处理的地方,也就不再细说了!

当我们完成了上面这些代码之后,我们就可以实际测试一下了,下面这张图就是一张实际测试时候的图

实现到这里的时候,大部分也就算完成了,但是我们发现我们在SplitContainer控件的MouseMove事件里判断鼠标位置并设置鼠标手势和收缩按钮的颜色

但是鼠标移出后必须要恢复的,

由于每一个控件都有自己的消息系统,当我们鼠标移出到Panel1或者Panel2的时候,SplitContainer控件的MouseMove 实际上并没有得到消息,

只有当我们鼠标在收缩按钮的横向那9个宽度或者高度的区域移动时才是真正的SplitContainer控件能拦截到的鼠标移动消息。

那我们就必须要加一点特殊处理了,当鼠标移动到Panel1或者Panel2中的时候恢复鼠标手势和散点小三角颜色,处理Panel1 和Panel2的 MouseEnter 事件

private void CtrlMouseEnter(object sender, EventArgs e)
{
if (this.Cursor == Cursors.Hand)
{
this.Cursor = Cursors.Default;
CollpaseColor = SystemColors.ButtonShadow;
this.Invalidate(this.SplitterRectangle); //局部刷新绘制
}
}

在此测试时,发现还有不完美,当Panel2或者Panel1中的控件又遮住它自身时,它自身的MouseEnter 又得不到消息了,所以我们还得继续添加处理:

为了不再啰嗦,就贴完整的代码咯,如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
namespace UserCtrl
{
[ToolboxBitmap(typeof(SplitContainer))]
public class SplitContainerEx : SplitContainer
{
enum MouseState
{
///
/// 正常
///
Normal,
///
/// 鼠标移入
///
Hover
}
public SplitContainerEx()
{
this.SetStyle(
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer, true);
this.SplitterWidth = 9;
this.Panel1MinSize = 0;
this.Panel2MinSize = 0;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new int SplitterWidth
{
get
{
return base.SplitterWidth;
}
set
{
base.SplitterWidth = 9;
}
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new int Panel1MinSize
{
get
{
return base.Panel1MinSize;
}
set
{
base.Panel1MinSize = 0;
}
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new int Panel2MinSize
{
get
{
return base.Panel2MinSize;
}
set
{
base.Panel2MinSize = 0;
}
}
public enum SplitterPanelEnum
{
Panel1,
Panel2
}
SplitterPanelEnum mCollpasePanel = SplitterPanelEnum.Panel2;
///
/// 进行折叠或展开的SplitterPanel
///
[DefaultValue(SplitterPanelEnum.Panel2)]
public SplitterPanelEnum CollpasePanel
{
get
{
return mCollpasePanel;
}
set
{
if (value != mCollpasePanel)
{
mCollpasePanel = value;
this.Invalidate(this.ControlRect);
}
}
}
bool mCollpased = false;
///
/// 是否为折叠状态
///
public bool IsCollpased
{
get { return mCollpased; }
}
Rectangle mRect = new Rectangle();
///
/// 控制器绘制区域
///
private Rectangle ControlRect
{
get
{
if (this.Orientation == Orientation.Horizontal)
{
mRect.X = this.Width <= 80 ? 0 : this.Width / 2 - 40;
mRect.Y = this.SplitterDistance;
mRect.Width = 80;
mRect.Height = 9;
}
else
{
mRect.X = this.SplitterDistance;
mRect.Y = this.Height <= 80 ? 0 : this.Height / 2 - 40;
mRect.Width = 9;
mRect.Height = 80;
}
return mRect;
}
}
///
/// 鼠标状态
///
MouseState mMouseState = MouseState.Normal;
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
//绘制参数
bool collpase = false;
if ((this.CollpasePanel == SplitterPanelEnum.Panel1 && mCollpased == false)
|| this.CollpasePanel == SplitterPanelEnum.Panel2 && mCollpased)
{
collpase = true;
}
Color color = mMouseState == MouseState.Normal ? SystemColors.ButtonShadow : SystemColors.ControlDarkDark;
//需要绘制的图片
Bitmap bmp = CreateControlImage(collpase, color);
//绘制区域
if (this.Orientation == Orientation.Vertical)
{
bmp.RotateFlip(RotateFlipType.Rotate90FlipX);
}
//清除绘制区域
e.Graphics.SetClip(this.SplitterRectangle); //这里需要注意一点就是需要清除拆分器整个区域,如果仅清除控制按钮区域,则会出现虚线状态
e.Graphics.Clear(this.BackColor);
//绘制
e.Graphics.DrawImage(bmp, this.ControlRect);
}
public new bool IsSplitterFixed
{
get
{
return base.IsSplitterFixed;
}
set
{
base.IsSplitterFixed = value;
//此处设计防止运行时更改base.IsSplitterFixed属性时导致mIsSplitterFixed变量判断失效
if (value && mIsSplitterFixed == false)
{
mIsSplitterFixed = true;
}
}
}
bool mIsSplitterFixed = true;
protected override void OnMouseMove(MouseEventArgs e)
{
//鼠标在控制按钮区域
if (this.SplitterRectangle.Contains(e.Location))
{
if (this.ControlRect.Contains(e.Location))
{
//如果拆分器可移动,则鼠标在控制按钮范围内时临时关闭拆分器
if (this.IsSplitterFixed == false)
{
this.IsSplitterFixed = true;
mIsSplitterFixed = false;
}
this.Cursor = Cursors.Hand;
mMouseState = MouseState.Hover;
this.Invalidate(this.ControlRect);
}
else
{
//如果拆分器为临时关闭,则开启拆分器
if (mIsSplitterFixed == false)
{
this.IsSplitterFixed = false;
if (this.Orientation == Orientation.Horizontal)
{
this.Cursor = Cursors.HSplit;
}
else
{
this.Cursor = Cursors.VSplit;
}
}
else
{
this.Cursor = Cursors.Default;
}
mMouseState = MouseState.Normal;
this.Invalidate(this.ControlRect);
}
}
base.OnMouseMove(e);
}
protected override void OnMouseLeave(EventArgs e)
{
this.Cursor = Cursors.Default;
mMouseState = MouseState.Normal;
this.Invalidate(this.ControlRect);
base.OnMouseLeave(e);
}
protected override void OnMouseClick(MouseEventArgs e)
{
if (this.ControlRect.Contains(e.Location))
{
CollpaseOrExpand();
}
base.OnMouseClick(e);
}
int _HeightOrWidth;
///
/// 折叠或展开
///
public void CollpaseOrExpand()
{
if (mCollpased)
{
mCollpased = false;
this.SplitterDistance = _HeightOrWidth;
}
else
{
mCollpased = true;
_HeightOrWidth = this.SplitterDistance;
if (CollpasePanel == SplitterPanelEnum.Panel1)
{
this.SplitterDistance = 0 ;
}
else
{
if(this.Orientation==Orientation.Horizontal)
{
this.SplitterDistance = this.Height - 9;
}
else
{
this.SplitterDistance = this.Width - 9;
}
}
}
this.Invalidate(this.ControlRect); //局部刷新绘制
}
///
/// 需要绘制的用于折叠窗口的按钮样式
///
///
///
///
private Bitmap CreateControlImage(bool collapse, Color color)
{
Bitmap bmp = new Bitmap(80, 9);
for (int x = 5; x <= 30; x += 5)
{
for (int y = 1; y <= 7; y += 3)
{
bmp.SetPixel(x, y, color);
}
}
for (int x = 50; x <= 75; x += 5)
{
for (int y = 1; y <= 7; y += 3)
{
bmp.SetPixel(x, y, color);
}
}
//控制小三角底边向上或者向下
if (collapse)
{
int k = 0;
for (int y = 7; y >= 1; y–)
{
for (int x = 35 + k; x <= 45 - k; x++)
{
bmp.SetPixel(x, y, color);
}
k++;
}
}
else
{
int k = 0;
for (int y = 1; y <= 7; y++)
{
for (int x = 35 + k; x <= 45 - k; x++)
{
bmp.SetPixel(x, y, color);
}
k++;
}
}
return bmp;
}
}
}

这下我们就可以将完整代码复制出来,自己来试试完整效果了。本文也就结束咯,更完美更细致的处理还需要我们在实际需要的时候进行细化,这里只展示一种大概的实现方式,欢迎大家打击!

SplitContainer控件扩展之收缩面板相关推荐

  1. (转)SplitContainer 控件(Windows 窗体)

    1.可以将 Windows 窗体 SplitContainer 控件看作是一个复合体,它是由一个可移动的拆分条分隔的两个面板.当鼠标指针悬停在该拆分条上时,指针将相应地改变形状以显示该拆分条是可移动的 ...

  2. 拆分器SplitContainer控件

    拆分器SplitContainer控件 原文:http://blog.csdn.net/warmspace2008/archive/2009/02/24/3934040.aspx 拆分器控件Split ...

  3. NET- SplitContainer控件说明

    拆分器控件Splitcontainer ,是一个含有Splitter拆分条的容器,它包含两个面板容器Panel1,Panel2,可以移动拆分条,对面板大小进行控制! 控件学习示例程序! 属性介绍; / ...

  4. MVC html 控件扩展【转载】

    项目中用到mvc2控件扩展,任务分给了我,开发完了,结果可能要用devexpress,费了不少功夫,网上查找资料,整理成符合项目的,自己留个备份吧,一起学习 DropDownTree 第一个控件是Dr ...

  5. wpf listview 添加控件_WPF开源控件扩展库 MaterialDesignExtensions

    WPF开源控件扩展库 - MaterialDesignExtensions MaterialDesignExtensions仓库截图 logo Material Design Extensions 在 ...

  6. RxSwift UI控件扩展

    RxSwift UI控件扩展 最好的示例是参考RxCocoa查看类似的属性如何扩展Rx化的. 为了配合RxSwift的绑定关系,RxCocoa提供简单的基于Cocoa控件的扩展,但是很少,比如Labe ...

  7. WPF开源控件扩展库 - MaterialDesignExtensions

    WPF开源控件扩展库 - MaterialDesignExtensions MaterialDesignExtensions仓库截图 logo Material Design Extensions 在 ...

  8. C#实现在Form1的SplitContainer控件中Form2的按钮打开Form3

    C#实现在Form1的SplitContainer控件中Form2的按钮打开Form3 问题描述 解决方法 问题描述 C#实现在Form1的SplitContainer控件中Form2的按钮打开For ...

  9. Silverlight实用窍门系列:59.多个中心点联动多线的可拖动控件扩展为拓扑图

    在本系列的第17篇文章中"Silverlight实用窍门系列:17.中心点联动多线的可拖动控件(绘制工程图.拓扑图基础) ",制作了基本的中心联动图标.有园友对此图的扩展不是很清晰 ...

  10. 玩转控件:对Dev的GridControl控件扩展

    缘由 一切实现来源于需求,目的在于不盲目造轮子,有小伙伴儿在看了<玩转控件:对Dev中GridControl控件的封装和扩展>文章后,私信作者说,因公司业务逻辑比较复杂,展示字段比较多,尤 ...

最新文章

  1. 英特尔联合Facebook研发AI芯片:CPU老厂能在AI时代打好翻身仗吗
  2. 当科学遇上众包:9个值得关注的前沿科技算力众包平台
  3. Isolation Forest原理总结
  4. 【Linux 内核】编译 Linux 内核 ⑤ ( 查看 .config 编译配置文件 | 正式编译内核 )
  5. Java8新特性Stream API与Lambda表达式详解(1)
  6. 深度学习教程 TensorFlow and Deep Learning Tutorials
  7. 用python恢复删除的文件_使用python删除N天前的文件
  8. CTO 太水!犯了低级错误,还删除代码隐藏证据!
  9. linux 音频驱动的流程,Intel平台下Linux音频驱动流程分析
  10. python中集合set,字典dict和列表list的区别以及用法
  11. object.prototype.call
  12. 图层样式混合选项_图标设计,用PS的图层样式制作逼真的玉石图标
  13. matlab第十章实验,matlab 图像函数以及运用(第十章)
  14. 通过SublimeCodeIntel设置JavaScript自动补全
  15. 没有学历文凭,如何成为一名优秀的 Java 程序员?
  16. (14)Python_SimpleImputer缺失值处理
  17. 【用游戏学C语言】几个基本的小游戏的实现(缓慢更新中~)
  18. HTTP协议之chunk介绍
  19. ViewFlipper用法
  20. Android 实现简单的悬浮窗按钮(一)

热门文章

  1. Pygame教程(预备篇)
  2. python numpy库下载_python3.6下Numpy库下载与安装图文教程
  3. VOC2007-2012数据集
  4. iOS开发之第三方支付-银联支付
  5. 使用ildasm反编译修改c# dll
  6. 2019泰迪杯C题案例分析-python大数据自动化数据挖掘
  7. html中字体 楷体_css设置各种中文字体如雅黑、黑体、宋体、楷体等等
  8. NCL做一个简单的EOF分析例子
  9. vue中引用BScroll监听上拉加载报错
  10. 维修手册 html 打开,汽车维修手册大全.pdf