Trackbar見了很多種,每種播放器的都有它自己風格的Trackbar,鄙人最近在寫一個屬於自己的播放器,但是不想使用VS工具箱里面的那個Trackbar,於是上網看了一下資料,自己也模仿地寫了一個。
其實寫這個控件,關鍵就是用GDI+來繪圖,對於這個Trackbar控件必要的屬性和行為(包括方法、事件),一個簡單的Trackbar就能做出來了。感覺這個就是一個GDI+章節的練習吧。
我寫的這個Trackbar是繼承Control類的,不是TrackBar,也不是UserControl類,又不是ScrollableControl類。
- Trackbar有以下外放的屬性
- MinValue:Trackerbar標尺的最小值
- MaxValue:Trackerbar標尺的最大值
- Value:Trackerbar滑塊的當前值
- FillColor:標尺上最小值到當前值的顏色
- EmptyColor:標尺上當前值到最大值間的顏色
- Shape:滑塊的形狀,目前只有兩種,圓形和矩形
接下來就展示利用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方法,每當控件重繪的時候就會調用這個方法了,整個繪制的流程大致就這樣
- 繪制整條標尺並填充EmptyColor;(第9行)
- 把標尺起點到當前值間的標尺顏色塗成FillColor;(第14行)
- 根據滑塊的形狀在當前值的位置繪制滑塊(17行和18行)
其中標尺的矩形高度是定了是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。
控件效果圖如下
整個控件的完整代碼如下

1 public class PlayerTrackBar : Control 2 { 3 protected const int BordHeight = 6; 4 5 public PlayerTrackBar() 6 { 7 this.MinValue = 0; 8 this.MaxValue = 100; 9 this.Value = 0; 10 this.Width = 200; 11 this.DoubleBuffered = true; 12 } 13 14 private int minValue, maxValue, currValue; 15 16 [ Description("最小值"),Category("值")] 17 public int MinValue 18 { 19 get { return minValue; } 20 set { 21 if (value > MaxValue) return; 22 minValue = value; 23 this.Refresh(); 24 } 25 } 26 27 [Description("最大值"), Category("值")] 28 public int MaxValue 29 { 30 get { return maxValue; } 31 set 32 { 33 if (value < MinValue) return; 34 maxValue = value; 35 this.Refresh(); 36 } 37 } 38 39 [Description("當前值"), Category("值")] 40 public int Value 41 { 42 get { return currValue; } 43 set 44 { 45 int preValue = currValue; 46 if (value > MaxValue) currValue = MaxValue; 47 else if (value < MinValue) currValue = MinValue; 48 else currValue = value; 49 this.Refresh(); 50 if (preValue != currValue) OnScroll(); 51 } 52 } 53 54 private Color fillColor= Color.White; 55 [Description("有值部分顏色"), Category("外觀")] 56 public Color FillColor 57 { 58 get { return fillColor; } 59 set { fillColor = value; } 60 } 61 62 private Color emptyColor = Color.FromArgb(135, 124, 124); 63 [Description("無值部分顏色"), Category("外觀")] 64 public Color EmptyColor 65 { 66 get { return emptyColor; } 67 set { emptyColor = value; } 68 } 69 70 [Description("滑塊形狀"), Category("外觀")] 71 public TrackShape Shape { get; set; } 72 73 protected float ValueX 74 { 75 get 76 { 77 return (Value - MinValue) / (float)(MaxValue - MinValue) * BorderLength; 78 } 79 } 80 81 protected int BorderLength 82 { 83 get 84 { 85 return this.Width - BordHeight*2; 86 } 87 } 88 89 protected void DrawCircleTrack(Graphics g,SolidBrush brush) 90 { 91 using (Pen emptyPen = new Pen(EmptyColor)) 92 { 93 g.FillEllipse(brush, ValueX, 2, 2 * BordHeight, 2 * BordHeight); 94 g.DrawEllipse(emptyPen, ValueX, 2, 2 * BordHeight, 2 * BordHeight); 95 } 96 } 97 98 protected void DrawRectangles(Graphics g,SolidBrush brush) 99 { 100 using (Pen emptyPen = new Pen(EmptyColor)) 101 { 102 g.FillRectangle(brush, ValueX + BordHeight / 2, 2, BordHeight, 2 * BordHeight); 103 g.DrawRectangle(emptyPen, ValueX + BordHeight / 2, 2, BordHeight, 2 * BordHeight); 104 } 105 } 106 107 /// <summary> 108 /// 通過鼠標當前位置計算出進度值 109 /// </summary> 110 /// <param name="x">鼠標當前位置</param> 111 /// <returns></returns> 112 protected int LocationX2Value(int x) 113 { 114 return (int)((MaxValue - MinValue) / (float)BorderLength * (x - BordHeight) + MinValue); 115 } 116 117 protected override void OnPaint(PaintEventArgs e) 118 { 119 using (Bitmap bit=new Bitmap(this.Width,this.Height)) 120 { 121 using (Graphics g=Graphics.FromImage(bit)) 122 { 123 using (SolidBrush emptyBrush=new SolidBrush(EmptyColor)) 124 { 125 g.FillRectangle(emptyBrush, BordHeight, 2 + BordHeight / 2f, BorderLength, BordHeight); 126 } 127 128 using (SolidBrush valueBrush=new SolidBrush(FillColor)) 129 { 130 g.FillRectangle(valueBrush, BordHeight+1f, 2 + BordHeight / 2f+1f, ValueX-2,BordHeight-2 ); 131 switch (Shape) 132 { 133 case TrackShape.Circle: DrawCircleTrack(g, valueBrush); break; 134 case TrackShape.Rectanles: DrawRectangles(g, valueBrush); break; 135 } 136 } 137 } 138 e.Graphics.DrawImage(bit, 0, 0); 139 } 140 } 141 142 protected override void OnMouseClick(MouseEventArgs e) 143 { 144 base.OnMouseClick(e); 145 if (e.Button != System.Windows.Forms.MouseButtons.Left) return; 146 int tempValue = LocationX2Value(e.X); 147 if (tempValue > MaxValue) Value = MaxValue; 148 else if (tempValue < MinValue) Value = MinValue; 149 else Value = tempValue; 150 } 151 152 protected override void OnMouseMove(MouseEventArgs e) 153 { 154 base.OnMouseMove(e); 155 if (e.Button != System.Windows.Forms.MouseButtons.Left) return; 156 int tempValue = LocationX2Value(e.X); 157 if (tempValue > MaxValue) Value = MaxValue; 158 else if (tempValue < MinValue) Value = MinValue; 159 else Value = tempValue; 160 } 161 162 [Description("當值變化后觸發的事件"), Category("值")] 163 public event EventHandler Scroll = null; 164 public virtual void OnScroll() 165 { 166 if (Scroll != null) 167 { 168 Scroll(this, new EventArgs()); 169 } 170 } 171 172 protected override void OnResize(EventArgs e) 173 { 174 base.OnResize(e); 175 this.Refresh(); 176 } 177 178 public enum TrackShape 179 { 180 Circle, 181 Rectanles 182 } 183 }
感覺這個控件如果要擴展的話,就拓展多一個垂直滑動,或者可以提供自定義滑塊。