Trackbar见了很多种,每种播放器的都有它自己风格的Trackbar,鄙人最近在写一个属于自己的播放器,但是不想使用VS工具箱里面的那个Trackbar,于是上网看了一下资料,自己也模仿地写了一个。
其实写这个控件,关键就是用GDI+来绘图,对于这个Trackbar控件必要的属性和行为(包括方法、事件),一个简单的Trackbar就能做出来了。感觉这个就是一个GDI+章节的练习吧。
我写的这个Trackbar是继承Control类的,不是TrackBar,也不是UserControl类,又不是ScrollableControl类。
接下来就展示利用GDI+绘制整个Trackbar的方法
1 protected override void OnPaint(PaintEventArgs e) 2 { 3 using (Bitmap bit=new Bitmap(this.Width,this.Height)) 4 { 5 using (Graphics g=Graphics.FromImage(bit)) 6 { 7 using (SolidBrush emptyBrush=new SolidBrush(EmptyColor)) 8 { 9 g.FillRectangle(emptyBrush, BordHeight, 2 + BordHeight / 2f, BorderLength, BordHeight); 10 } 11 12 using (SolidBrush valueBrush=new SolidBrush(FillColor)) 13 { 14 g.FillRectangle(valueBrush, BordHeight+1f, 2 + BordHeight / 2f+1f, ValueX-2,BordHeight-2 ); 15 switch (Shape) 16 { 17 case TrackShape.Circle: DrawCircleTrack(g, valueBrush); break; 18 case TrackShape.Rectanles: DrawRectangles(g, valueBrush); break; 19 } 20 } 21 } 22 e.Graphics.DrawImage(bit, 0, 0); 23 } 24 } 25
此处是重写了Control类的OnPaint方法,每当控件重绘的时候就会调用这个方法了,整个绘制的流程大致就这样
其中标尺的矩形高度是定了是3,宽度就是与整个控件的宽度相差6,也就是控件左右边距各留3个像素的补白。
还定义了两个方法分别绘制两种滑块
圆形滑块的半径就与标尺的高度相等,圆心的位置就在当前值的刻度线的中点。
protected void DrawCircleTrack(Graphics g,SolidBrush brush) { using (Pen emptyPen = new Pen(EmptyColor)) { g.FillEllipse(brush, ValueX, 2, 2 * BordHeight, 2 * BordHeight); g.DrawEllipse(emptyPen, ValueX, 2, 2 * BordHeight, 2 * BordHeight); } }
矩形滑块的宽度与标尺的高度相等,滑块的高度刚好是标尺高度的两倍,矩形滑块的中心是跟当前值的刻度线的中点重合。
protected void DrawRectangles(Graphics g,SolidBrush brush) { using (Pen emptyPen = new Pen(EmptyColor)) { g.FillRectangle(brush, ValueX + BordHeight / 2, 2, BordHeight, 2 * BordHeight); g.DrawRectangle(emptyPen, ValueX + BordHeight / 2, 2, BordHeight, 2 * BordHeight); } }
控件还重写了Control基类的几个方法,以待控件触发了某些事件时调用,目的就是实现滑块滑动和跳动的效果。
protected override void OnMouseClick(MouseEventArgs e) { base.OnMouseClick(e); if (e.Button != System.Windows.Forms.MouseButtons.Left) return; int tempValue = LocationX2Value(e.X); if (tempValue > MaxValue) Value = MaxValue; else if (tempValue < MinValue) Value = MinValue; else Value = tempValue; }
这个是当鼠标单击控件时执行的方法,根据鼠标点击的位置,通过数学上一次函数计算出相应的当前值。以实现滑块跳动到当前鼠标点击的刻度上。
protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (e.Button != System.Windows.Forms.MouseButtons.Left) return; int tempValue = LocationX2Value(e.X); if (tempValue > MaxValue) Value = MaxValue; else if (tempValue < MinValue) Value = MinValue; else Value = tempValue; }
这个是鼠标上的某个键在控件上拖动时执行的方法,跟上面的原理一样,实现滑块滑动的效果。
protected override void OnResize(EventArgs e) { base.OnResize(e); this.Refresh(); }
这个是控件的大小发生变化时执行,为了让控件重绘。
我这里还模仿VS的Trackbar的Scroll事件那样,外放了一个Scroll事件,实际上触发的条件就是当前值发生变化。因为在滑块滑动的时候本质上就是当前值在变化
public event EventHandler Scroll = null; public virtual void OnScroll() { if (Scroll != null) { Scroll(this, new EventArgs()); } }
这里没有去继承ScrollableControl类,去重写它的OnScroll方法。主要是没有用上它的OldValue和NewValue。
控件效果图如下
整个控件的完整代码如下
public class PlayerTrackBar : Control
{
protected const int BordHeight = 6;
public PlayerTrackBar()
{
this.MinValue = 0;
this.MaxValue = 100;
this.Value = 0;
this.Width = 200;
this.DoubleBuffered = true;
}
private int minValue, maxValue, currValue;
[ Description("最小值"),Category("值")]
public int MinValue
{
get { return minValue; }
set {
if (value > MaxValue) return;
minValue = value;
this.Refresh();
}
}
[Description("最大值"), Category("值")]
public int MaxValue
{
get { return maxValue; }
set
{
if (value < MinValue) return;
maxValue = value;
this.Refresh();
}
}
[Description("当前值"), Category("值")]
public int Value
{
get { return currValue; }
set
{
int preValue = currValue;
if (value > MaxValue) currValue = MaxValue;
else if (value < MinValue) currValue = MinValue;
else currValue = value;
this.Refresh();
if (preValue != currValue) OnScroll();
}
}
private Color fillColor= Color.White;
[Description("有值部分颜色"), Category("外观")]
public Color FillColor
{
get { return fillColor; }
set { fillColor = value; }
}
private Color emptyColor = Color.FromArgb(135, 124, 124);
[Description("无值部分颜色"), Category("外观")]
public Color EmptyColor
{
get { return emptyColor; }
set { emptyColor = value; }
}
[Description("滑块形状"), Category("外观")]
public TrackShape Shape { get; set; }
protected float ValueX
{
get
{
return (Value - MinValue) / (float)(MaxValue - MinValue) * BorderLength;
}
}
protected int BorderLength
{
get
{
return this.Width - BordHeight*2;
}
}
protected void DrawCircleTrack(Graphics g,SolidBrush brush)
{
using (Pen emptyPen = new Pen(EmptyColor))
{
g.FillEllipse(brush, ValueX, 2, 2 * BordHeight, 2 * BordHeight);
g.DrawEllipse(emptyPen, ValueX, 2, 2 * BordHeight, 2 * BordHeight);
}
}
protected void DrawRectangles(Graphics g,SolidBrush brush)
{
using (Pen emptyPen = new Pen(EmptyColor))
{
g.FillRectangle(brush, ValueX + BordHeight / 2, 2, BordHeight, 2 * BordHeight);
g.DrawRectangle(emptyPen, ValueX + BordHeight / 2, 2, BordHeight, 2 * BordHeight);
}
}
/// <summary>
/// 通过鼠标当前位置计算出进度值
/// </summary>
/// <param name="x">鼠标当前位置</param>
/// <returns></returns>
protected int LocationX2Value(int x)
{
return (int)((MaxValue - MinValue) / (float)BorderLength * (x - BordHeight) + MinValue);
}
protected override void OnPaint(PaintEventArgs e)
{
using (Bitmap bit=new Bitmap(this.Width,this.Height))
{
using (Graphics g=Graphics.FromImage(bit))
{
using (SolidBrush emptyBrush=new SolidBrush(EmptyColor))
{
g.FillRectangle(emptyBrush, BordHeight, 2 + BordHeight / 2f, BorderLength, BordHeight);
}
using (SolidBrush valueBrush=new SolidBrush(FillColor))
{
g.FillRectangle(valueBrush, BordHeight+1f, 2 + BordHeight / 2f+1f, ValueX-2,BordHeight-2 );
switch (Shape)
{
case TrackShape.Circle: DrawCircleTrack(g, valueBrush); break;
case TrackShape.Rectanles: DrawRectangles(g, valueBrush); break;
}
}
}
e.Graphics.DrawImage(bit, 0, 0);
}
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
if (e.Button != System.Windows.Forms.MouseButtons.Left) return;
int tempValue = LocationX2Value(e.X);
if (tempValue > MaxValue) Value = MaxValue;
else if (tempValue < MinValue) Value = MinValue;
else Value = tempValue;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.Button != System.Windows.Forms.MouseButtons.Left) return;
int tempValue = LocationX2Value(e.X);
if (tempValue > MaxValue) Value = MaxValue;
else if (tempValue < MinValue) Value = MinValue;
else Value = tempValue;
}
[Description("当值变化后触发的事件"), Category("值")]
public event EventHandler Scroll = null;
public virtual void OnScroll()
{
if (Scroll != null)
{
Scroll(this, new EventArgs());
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
this.Refresh();
}
public enum TrackShape
{
Circle,
Rectanles
}
}
PlayerTrackBar
感觉这个控件如果要扩展的话,就拓展多一个垂直滑动,或者可以提供自定义滑块。最近比较少写博客,之前在园子里看过一篇文章,里面说过就算没人看也要坚持写博客。