FlexLabel——控制伸缩面板的标签
前 注:
这是自己平时根据自己需要写的一些小代码,未必对各看官有用。另外,这是根据个人想法而写,未必严谨和符合设计原则,若有任何不妥之处,还请不吝赐教。
说 明:
在豆瓣和其它一些网站陆续看到这样的UI效果:单击一篇文章的标题,在下边展开一个面板,显示文章的内容;再次单击则将文章内容面板收缩。
此控件是仿此效果的一个WinForm的实现。
此控件显示为了一个水平的标签条,左边可显示文本,右边显示一个向上或向下的箭头(描述当然相关面板处于展开还是收缩状态)。将鼠标置于控件上时,控件背景将高亮。
使用时,将要展开和收缩的面板关联到此控件并设置好其它相关属性即可。在此控件上单击鼠标时,此控件将通过设置目标控件的高度来达到展开或收缩的效果,展开或收缩的过程应用了阻尼效果。
设计要点&使用说明:
1、Target属性描述此控件关联的面板,IsSpread描述目标面板当前处于展开还是收缩状态。
2、SizeSpread和SizeShrink分别描述目标控件展开或收缩时的尺寸(高度)。
3、阻尼效果指目标控件的尺寸呈正弦曲线形变化。TickCount和TickPause用于控制阻尼效果,TickCount用于控制变化过程分几步完成,TickPause用于控制每两次变化之间的时间间隔。TickCount越大,变化过程越精细;TickCount越小、TickPause越小,收缩或展开的过程越快。
4、Flexing事件发生于IsSpread即将发生变化时,Flexed事件发生于IsSpread变化完成之后。
5、此控件的展示效果比较丑(因为是自己绘制的,在这方面,我没有天赋),如果哪位能够实现更好的效果,还请不吝赐教。
源代码 :
代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace CommonLibrary.ExtendedControl
{
/// <summary>
/// 带有伸缩功能的一个标签
/// </summary>
public partial class FlexLabel : Control
{
#region 数据
#region 控件绘制相关
/// <summary>
/// 是否高亮
/// </summary>
private bool _IsHighLight = false;
/// <summary>
/// 是否处于伸展状态
/// </summary>
private bool _IsSpread = false;
/// <summary>
/// 状态变化:此变量用于防止使用者在Flexing事件响应中修改IsSpread属性
/// </summary>
private bool StateChanging = false;
/// <summary>
/// 高亮区域的边界路径
/// </summary>
System.Drawing.Drawing2D.GraphicsPath HighLightRegionPath;
/// <summary>
/// 高亮颜色
/// </summary>
Color _HighLightColor;
/// <summary>
/// 隐藏时的颜色
/// </summary>
Color _HideColor;
/// <summary>
/// 用于填充高亮区域的刷子
/// </summary>
Brush HighLightBrush;
/// <summary>
/// 用于清空高亮区域的刷子
/// </summary>
Brush HideBrush;
/// <summary>
/// 文本画刷
/// </summary>
Brush TextBrush;
/// <summary>
/// 描述信息
/// </summary>
string _Description;
private int _LabelStyle = 0;
#endregion
#region 关联控件数据与伸缩效果
/// <summary>
/// 目标
/// </summary>
Control _Target;
/// <summary>
/// 收缩时的尺寸
/// </summary>
int _SizeShrink = 10;
/// <summary>
/// 伸展时的尺寸
/// </summary>
int _SizeSpread = 10;
/// <summary>
/// 阻尼效果的间隔角度
/// </summary>
double RadianTick = 5 * (Math.PI / 180);
/// <summary>
/// 指示伸缩过程分为多个部分完成
/// </summary>
int _TickCount = 10;
/// <summary>
/// 指定两个部分伸缩动作间的时间间隔,以毫秒为单位
/// </summary>
int _TickPause = 50;
#endregion
CancelEventArgs FlexingArgs;
#endregion
#region 属性
/// <summary>
/// 是否处于伸展状态
/// </summary>
public bool IsSpread
{
get { return _IsSpread; }
set
{
if (_IsSpread == value || StateChanging) return;
StateChanging = true;
FlexingArgs.Cancel = false;
if (_Flexing != null) _Flexing(this, FlexingArgs);
if (FlexingArgs.Cancel)
{
StateChanging = false;
return;
}
_IsSpread = value;
FlexTarget();
this.Refresh();
StateChanging = false;
if (_Flexed != null) _Flexed(this, System.EventArgs.Empty);
}
}
#region 控件绘制相关
/// <summary>
/// 是否高亮
/// </summary>
private bool IsHighLight
{
get { return _IsHighLight; }
set
{
if (_IsHighLight == value) return;
_IsHighLight = value;
this.Refresh();
}
}
/// <summary>
/// 高亮颜色
/// </summary>
public Color HighLightColor
{
get { return _HighLightColor; }
set
{
if (_HighLightColor == value) return;
_HighLightColor = value;
HighLightBrush = new SolidBrush(_HighLightColor);
}
}
/// <summary>
/// 隐藏颜色
/// </summary>
public Color HideColor
{
get { return _HideColor; }
set
{
if (_HideColor == value) return;
_HideColor = value;
HideBrush = new SolidBrush(_HideColor);
}
}
/// <summary>
/// 显示的提示信息
/// </summary>
[Browsable(true)]
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
}
}
/// <summary>
/// 标签样式
/// </summary>
public int LabelStyle
{
get { return _LabelStyle; }
set { _LabelStyle = value; }
}
#endregion
#region 关联控件与伸缩效果
/// <summary>
/// 关联的控件
/// </summary>
public Control Target
{
get { return _Target; }
set { _Target = value; }
}
/// <summary>
/// 朝向
/// </summary>
public Orientation Orientation
{
get { return Orientation.Vertical; }
set { }
}
/// <summary>
/// 收缩时的尺寸
/// </summary>
public int SizeShrink
{
get { return _SizeShrink; }
set
{
if (value < 0) return;
_SizeShrink = value;
}
}
/// <summary>
/// 伸展时的尺寸
/// </summary>
public int SizeSpread
{
get { return _SizeSpread; }
set
{
if (value < _SizeShrink) return;
_SizeSpread = value;
}
}
/// <summary>
/// 此值用于指定将伸缩过程分为多少次完成。此值越大,则伸缩过程的平滑效果越好,但代价也越大。推荐在8至20之间。
/// </summary>
public int TickCount
{
get { return _TickCount; }
set
{
if (_TickCount < 1)
{
_TickCount = 1;
}
else if (_TickCount > 30)
{
TickCount = 30;
}
else
{
_TickCount = value;
}
RadianTick = Math.PI / (_TickCount * 2);
}
}
/// <summary>
/// 指定两个部分伸缩动作间的时间间隔,以毫秒为单位
/// </summary>
public int TickPause
{
get { return _TickPause; }
set
{
if (_TickPause < 0)
{
_TickPause = 0;
}
else
{
_TickPause = value;
}
}
}
#endregion
#endregion
#region 事件
/// <summary>
/// 伸缩事件
/// </summary>
private event CancelEventHandler _Flexing;
/// <summary>
/// 伸缩事件
/// </summary>
public event CancelEventHandler Flexing
{
add
{
_Flexing += value;
}
remove
{
_Flexing -= value;
}
}
/// <summary>
/// 伸缩发生后事件
/// </summary>
private event EventHandler _Flexed;
/// <summary>
/// 伸缩发生后事件
/// </summary>
public event EventHandler Flexed
{
add
{
_Flexed += value;
}
remove
{
_Flexed -= value;
}
}
#endregion
#region 方法
public FlexLabel()
{
InitializeComponent();
#region 画笔
//Graphics = this.CreateGraphics();
HighLightRegionPath = GetLRRoundRegionPath(this.ClientRectangle);
HighLightColor = Color.FromKnownColor(KnownColor.Control);
HideColor = Color.FromKnownColor(KnownColor.Window);
TextBrush = new SolidBrush(Color.FromKnownColor(KnownColor.Black));
#endregion
this.Padding = new Padding(3, 3, 20, 3);
FlexingArgs = new CancelEventArgs(false);
}
/// <summary>
/// 重置伸缩状态
/// </summary>
/// <param name="IsSpread"></param>
public void ResetFlex(bool IsSpread)
{
_IsSpread = IsSpread;
this.Refresh();
}
protected override void OnPaint(PaintEventArgs pe)
{
// TODO: Add custom paint code here
// Calling the base class OnPaint
base.OnPaint(pe);
float YPos = Math.Max(0, (this.Height - 10)) / 2;
if (HighLightRegionPath == null) return;
pe.Graphics.FillRectangle(HideBrush, this.ClientRectangle);
if (IsHighLight) pe.Graphics.FillPath(HighLightBrush, HighLightRegionPath);
pe.Graphics.DrawString(this.Text, this.Font, this.TextBrush, this.Padding.Left, YPos);
string FlexTag = (IsSpread) ? "︽" : "︾";
pe.Graphics.DrawString(FlexTag, this.Font, this.TextBrush, (float)(this.Width - this.Padding.Right), YPos);
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
Console.WriteLine("SizeChanged");
HighLightRegionPath = GetLRRoundRegionPath(this.ClientRectangle);
//Graphics = this.CreateGraphics();
}
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
IsHighLight = true;
//Graphics.FillPath(HighLightBrush, HighLightRegionPath);
//Graphics.DrawString(Description, this.Font, this.TextBrush, 8f, 3f);
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
IsHighLight = false;
//Graphics.FillPath(HideBrush, HighLightRegionPath);
//Graphics.DrawString(Description, this.Font, this.TextBrush, 8f, 3f);
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
IsSpread = !IsSpread;
}
/// <summary>
/// 获取一个左右两端为弧形的区域
/// </summary>
/// <param name="Rect">包含区域的矩形</param>
/// <returns></returns>
public System.Drawing.Drawing2D.GraphicsPath GetLRRoundRegionPath(Rectangle Rect)
{
System.Drawing.Drawing2D.GraphicsPath Path = null;
Rectangle ArcRect;
try
{
Path = new System.Drawing.Drawing2D.GraphicsPath();
switch (LabelStyle)
{
case 0:
#region 两端圆滑
ArcRect = new Rectangle(Rect.Location, new Size(Rect.Height, Rect.Height));
//左边
Path.AddArc(ArcRect, 90, 180);
//右边
ArcRect.X = Rect.X + Rect.Width - Rect.Height;
Path.AddArc(ArcRect, 270, 180);
break;
#endregion
case 1:
#region 四角圆滑
ArcRect = new Rectangle(Rect.Location, new Size(8, 8));
//左上角
Path.AddArc(ArcRect, 180, 90);
ArcRect.X = Rect.Width - 9;
//右上角
Path.AddArc(ArcRect, 270, 90);
ArcRect.Y = Rect.Height - 9;
//右下角
Path.AddArc(ArcRect, 0, 90);
ArcRect.X = 0;
//左下角
Path.AddArc(ArcRect, 90, 90);
break;
#endregion
case 2:
#region 四角平滑
Path.AddLine(0, 2, 2, 0);
Path.AddLine(Rect.Width - 2, 0, Rect.Width, 2);
Path.AddLine(Rect.Width, Rect.Height - 2, Rect.Width - 2, Rect.Height);
Path.AddLine(2, Rect.Height, 0, Rect.Height - 2);
break;
#endregion
default:
break;
}
//闭合
Path.CloseFigure();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return Path;
}
/// <summary>
/// 伸缩目标控件
/// </summary>
public void FlexTarget()
{
if (Target == null || SizeShrink == SizeSpread) return;
int Distance = SizeSpread - SizeShrink;
double Radian = 0;
double RadianTick = this.IsSpread ? this.RadianTick : -this.RadianTick;
int SizeInit = this.IsSpread ? this.SizeShrink : this.SizeSpread;
for (int i = 0; i < _TickCount; i++)
{
Radian += RadianTick;
Target.Height = SizeInit + (int)(Distance * Math.Sin(Radian));
Target.Update();
System.Threading.Thread.Sleep(_TickPause);
}
}
#endregion
}
}
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace CommonLibrary.ExtendedControl
{
/// <summary>
/// 带有伸缩功能的一个标签
/// </summary>
public partial class FlexLabel : Control
{
#region 数据
#region 控件绘制相关
/// <summary>
/// 是否高亮
/// </summary>
private bool _IsHighLight = false;
/// <summary>
/// 是否处于伸展状态
/// </summary>
private bool _IsSpread = false;
/// <summary>
/// 状态变化:此变量用于防止使用者在Flexing事件响应中修改IsSpread属性
/// </summary>
private bool StateChanging = false;
/// <summary>
/// 高亮区域的边界路径
/// </summary>
System.Drawing.Drawing2D.GraphicsPath HighLightRegionPath;
/// <summary>
/// 高亮颜色
/// </summary>
Color _HighLightColor;
/// <summary>
/// 隐藏时的颜色
/// </summary>
Color _HideColor;
/// <summary>
/// 用于填充高亮区域的刷子
/// </summary>
Brush HighLightBrush;
/// <summary>
/// 用于清空高亮区域的刷子
/// </summary>
Brush HideBrush;
/// <summary>
/// 文本画刷
/// </summary>
Brush TextBrush;
/// <summary>
/// 描述信息
/// </summary>
string _Description;
private int _LabelStyle = 0;
#endregion
#region 关联控件数据与伸缩效果
/// <summary>
/// 目标
/// </summary>
Control _Target;
/// <summary>
/// 收缩时的尺寸
/// </summary>
int _SizeShrink = 10;
/// <summary>
/// 伸展时的尺寸
/// </summary>
int _SizeSpread = 10;
/// <summary>
/// 阻尼效果的间隔角度
/// </summary>
double RadianTick = 5 * (Math.PI / 180);
/// <summary>
/// 指示伸缩过程分为多个部分完成
/// </summary>
int _TickCount = 10;
/// <summary>
/// 指定两个部分伸缩动作间的时间间隔,以毫秒为单位
/// </summary>
int _TickPause = 50;
#endregion
CancelEventArgs FlexingArgs;
#endregion
#region 属性
/// <summary>
/// 是否处于伸展状态
/// </summary>
public bool IsSpread
{
get { return _IsSpread; }
set
{
if (_IsSpread == value || StateChanging) return;
StateChanging = true;
FlexingArgs.Cancel = false;
if (_Flexing != null) _Flexing(this, FlexingArgs);
if (FlexingArgs.Cancel)
{
StateChanging = false;
return;
}
_IsSpread = value;
FlexTarget();
this.Refresh();
StateChanging = false;
if (_Flexed != null) _Flexed(this, System.EventArgs.Empty);
}
}
#region 控件绘制相关
/// <summary>
/// 是否高亮
/// </summary>
private bool IsHighLight
{
get { return _IsHighLight; }
set
{
if (_IsHighLight == value) return;
_IsHighLight = value;
this.Refresh();
}
}
/// <summary>
/// 高亮颜色
/// </summary>
public Color HighLightColor
{
get { return _HighLightColor; }
set
{
if (_HighLightColor == value) return;
_HighLightColor = value;
HighLightBrush = new SolidBrush(_HighLightColor);
}
}
/// <summary>
/// 隐藏颜色
/// </summary>
public Color HideColor
{
get { return _HideColor; }
set
{
if (_HideColor == value) return;
_HideColor = value;
HideBrush = new SolidBrush(_HideColor);
}
}
/// <summary>
/// 显示的提示信息
/// </summary>
[Browsable(true)]
public override string Text
{
get
{
return base.Text;
}
set
{
base.Text = value;
}
}
/// <summary>
/// 标签样式
/// </summary>
public int LabelStyle
{
get { return _LabelStyle; }
set { _LabelStyle = value; }
}
#endregion
#region 关联控件与伸缩效果
/// <summary>
/// 关联的控件
/// </summary>
public Control Target
{
get { return _Target; }
set { _Target = value; }
}
/// <summary>
/// 朝向
/// </summary>
public Orientation Orientation
{
get { return Orientation.Vertical; }
set { }
}
/// <summary>
/// 收缩时的尺寸
/// </summary>
public int SizeShrink
{
get { return _SizeShrink; }
set
{
if (value < 0) return;
_SizeShrink = value;
}
}
/// <summary>
/// 伸展时的尺寸
/// </summary>
public int SizeSpread
{
get { return _SizeSpread; }
set
{
if (value < _SizeShrink) return;
_SizeSpread = value;
}
}
/// <summary>
/// 此值用于指定将伸缩过程分为多少次完成。此值越大,则伸缩过程的平滑效果越好,但代价也越大。推荐在8至20之间。
/// </summary>
public int TickCount
{
get { return _TickCount; }
set
{
if (_TickCount < 1)
{
_TickCount = 1;
}
else if (_TickCount > 30)
{
TickCount = 30;
}
else
{
_TickCount = value;
}
RadianTick = Math.PI / (_TickCount * 2);
}
}
/// <summary>
/// 指定两个部分伸缩动作间的时间间隔,以毫秒为单位
/// </summary>
public int TickPause
{
get { return _TickPause; }
set
{
if (_TickPause < 0)
{
_TickPause = 0;
}
else
{
_TickPause = value;
}
}
}
#endregion
#endregion
#region 事件
/// <summary>
/// 伸缩事件
/// </summary>
private event CancelEventHandler _Flexing;
/// <summary>
/// 伸缩事件
/// </summary>
public event CancelEventHandler Flexing
{
add
{
_Flexing += value;
}
remove
{
_Flexing -= value;
}
}
/// <summary>
/// 伸缩发生后事件
/// </summary>
private event EventHandler _Flexed;
/// <summary>
/// 伸缩发生后事件
/// </summary>
public event EventHandler Flexed
{
add
{
_Flexed += value;
}
remove
{
_Flexed -= value;
}
}
#endregion
#region 方法
public FlexLabel()
{
InitializeComponent();
#region 画笔
//Graphics = this.CreateGraphics();
HighLightRegionPath = GetLRRoundRegionPath(this.ClientRectangle);
HighLightColor = Color.FromKnownColor(KnownColor.Control);
HideColor = Color.FromKnownColor(KnownColor.Window);
TextBrush = new SolidBrush(Color.FromKnownColor(KnownColor.Black));
#endregion
this.Padding = new Padding(3, 3, 20, 3);
FlexingArgs = new CancelEventArgs(false);
}
/// <summary>
/// 重置伸缩状态
/// </summary>
/// <param name="IsSpread"></param>
public void ResetFlex(bool IsSpread)
{
_IsSpread = IsSpread;
this.Refresh();
}
protected override void OnPaint(PaintEventArgs pe)
{
// TODO: Add custom paint code here
// Calling the base class OnPaint
base.OnPaint(pe);
float YPos = Math.Max(0, (this.Height - 10)) / 2;
if (HighLightRegionPath == null) return;
pe.Graphics.FillRectangle(HideBrush, this.ClientRectangle);
if (IsHighLight) pe.Graphics.FillPath(HighLightBrush, HighLightRegionPath);
pe.Graphics.DrawString(this.Text, this.Font, this.TextBrush, this.Padding.Left, YPos);
string FlexTag = (IsSpread) ? "︽" : "︾";
pe.Graphics.DrawString(FlexTag, this.Font, this.TextBrush, (float)(this.Width - this.Padding.Right), YPos);
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
Console.WriteLine("SizeChanged");
HighLightRegionPath = GetLRRoundRegionPath(this.ClientRectangle);
//Graphics = this.CreateGraphics();
}
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
IsHighLight = true;
//Graphics.FillPath(HighLightBrush, HighLightRegionPath);
//Graphics.DrawString(Description, this.Font, this.TextBrush, 8f, 3f);
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
IsHighLight = false;
//Graphics.FillPath(HideBrush, HighLightRegionPath);
//Graphics.DrawString(Description, this.Font, this.TextBrush, 8f, 3f);
}
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
IsSpread = !IsSpread;
}
/// <summary>
/// 获取一个左右两端为弧形的区域
/// </summary>
/// <param name="Rect">包含区域的矩形</param>
/// <returns></returns>
public System.Drawing.Drawing2D.GraphicsPath GetLRRoundRegionPath(Rectangle Rect)
{
System.Drawing.Drawing2D.GraphicsPath Path = null;
Rectangle ArcRect;
try
{
Path = new System.Drawing.Drawing2D.GraphicsPath();
switch (LabelStyle)
{
case 0:
#region 两端圆滑
ArcRect = new Rectangle(Rect.Location, new Size(Rect.Height, Rect.Height));
//左边
Path.AddArc(ArcRect, 90, 180);
//右边
ArcRect.X = Rect.X + Rect.Width - Rect.Height;
Path.AddArc(ArcRect, 270, 180);
break;
#endregion
case 1:
#region 四角圆滑
ArcRect = new Rectangle(Rect.Location, new Size(8, 8));
//左上角
Path.AddArc(ArcRect, 180, 90);
ArcRect.X = Rect.Width - 9;
//右上角
Path.AddArc(ArcRect, 270, 90);
ArcRect.Y = Rect.Height - 9;
//右下角
Path.AddArc(ArcRect, 0, 90);
ArcRect.X = 0;
//左下角
Path.AddArc(ArcRect, 90, 90);
break;
#endregion
case 2:
#region 四角平滑
Path.AddLine(0, 2, 2, 0);
Path.AddLine(Rect.Width - 2, 0, Rect.Width, 2);
Path.AddLine(Rect.Width, Rect.Height - 2, Rect.Width - 2, Rect.Height);
Path.AddLine(2, Rect.Height, 0, Rect.Height - 2);
break;
#endregion
default:
break;
}
//闭合
Path.CloseFigure();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return Path;
}
/// <summary>
/// 伸缩目标控件
/// </summary>
public void FlexTarget()
{
if (Target == null || SizeShrink == SizeSpread) return;
int Distance = SizeSpread - SizeShrink;
double Radian = 0;
double RadianTick = this.IsSpread ? this.RadianTick : -this.RadianTick;
int SizeInit = this.IsSpread ? this.SizeShrink : this.SizeSpread;
for (int i = 0; i < _TickCount; i++)
{
Radian += RadianTick;
Target.Height = SizeInit + (int)(Distance * Math.Sin(Radian));
Target.Update();
System.Threading.Thread.Sleep(_TickPause);
}
}
#endregion
}
}