在本人的上一篇随笔<<高仿QQMusic播放器,浅谈WinForm关于UI的制作>>一文中,本人对播放器列表右边的灰色滚动条极为不满意,也影响到整个软件UI的协调性,遂下决心要重绘一个符合自己UI风格的滚动条.
查了很多资料,都找不到直接重写ListBox滚动条的方法,只能曲线救国,先自己重绘一个带皮肤的滚动条,然后让它取代ListBox现有的滚动条.
老习惯,先传个效果图,你觉得感兴趣就继续看下去,不喜欢的话就此打住,懒得耽误你宝
贵的时间,嘿嘿
注意,此图中的滚动条宽度明显小于ListBox本身滚动条的宽度,我目前只顾着实现功能了,毕竟,宽度调整相当简单哈。
下面简单介绍下重绘系统滚动条的详细步骤:
1.在项目中添加新项--用户控件,我们命名为CustomScrollbar.cs
2.准备几张图片添加进项目资源作为滚动条重绘时要用的背景,我用的图片如下:
uparrow.png资源名称为uparrow ,滚动条的上箭头
ThumbBottom.png资源名称为ThumbBottom ,滚动条中间滑道的背景
ThumbMiddle.png资源名称为ThumbMiddle ,滚动条的中间的拖动块
downarrow.png资源名称为downarrow ,滚动条的下箭头
3.然后就是利用上面图片做背景重画滚动条背景了,直接给出CustomScrollbar.cs的代码吧


2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Drawing;
5 using System.Data;
6 using System.Text;
7 using System.Windows.Forms;
8 using System.Windows.Forms.Design;
9 using System.Diagnostics;
10 namespace Winamp
11 {
12 [Designer( typeof (ScrollbarControlDesigner))]
13 public partial class CustomScrollbar : UserControl
14 {
15
16 protected Color moChannelColor = Color.Empty;
17 protected Image moUpArrowImage = null ;
18 protected Image moDownArrowImage = null ;
19 protected Image moThumbArrowImage = null ;
20
21 protected Image moThumbTopImage = null ;
22 protected Image moThumbTopSpanImage = null ;
23 protected Image moThumbBottomImage = null ;
24 protected Image moThumbBottomSpanImage = null ;
25 protected Image moThumbMiddleImage = null ;
26
27 protected int moLargeChange = 10 ;
28 protected int moSmallChange = 1 ;
29 protected int moMinimum = 0 ;
30 protected int moMaximum = 100 ;
31 protected int moValue = 0 ;
32 private int nClickPoint;
33
34 protected int moThumbTop = 0 ;
35
36 protected bool moAutoSize = false ;
37
38 private bool moThumbDown = false ;
39 private bool moThumbDragging = false ;
40
41 public new event EventHandler Scroll = null ;
42 public event EventHandler ValueChanged = null ;
43
44 private int GetThumbHeight()
45 {
46 int nTrackHeight = ( this .Height - (UpArrowImage.Height + DownArrowImage.Height));
47 float fThumbHeight = (( float )LargeChange / ( float )Maximum) * nTrackHeight;
48 int nThumbHeight = ( int )fThumbHeight;
49
50 if (nThumbHeight > nTrackHeight)
51 {
52 nThumbHeight = nTrackHeight;
53 fThumbHeight = nTrackHeight;
54 }
55 if (nThumbHeight < 56 )
56 {
57 nThumbHeight = 56 ;
58 fThumbHeight = 56 ;
59 }
60
61 return nThumbHeight;
62 }
63
64 public CustomScrollbar()
65 {
66
67 InitializeComponent();
68 SetStyle(ControlStyles.ResizeRedraw, true );
69 SetStyle(ControlStyles.AllPaintingInWmPaint, true );
70 SetStyle(ControlStyles.DoubleBuffer, true );
71
72 moChannelColor = Color.FromArgb( 51 , 166 , 3 );
73 UpArrowImage = BASSSkin.uparrow;
74 DownArrowImage = BASSSkin.downarrow;
75
76
77 ThumbBottomImage = BASSSkin.ThumbBottom;
78
79 ThumbMiddleImage = BASSSkin.ThumbMiddle;
80
81 this .Width = UpArrowImage.Width;
82 base .MinimumSize = new Size(UpArrowImage.Width, UpArrowImage.Height + DownArrowImage.Height + GetThumbHeight());
83 }
84
85 [EditorBrowsable(EditorBrowsableState.Always), Browsable( true ), DefaultValue( false ), Category( " Behavior " ), Description
86
87 ( " LargeChange " )]
88 public int LargeChange
89 {
90 get { return moLargeChange; }
91 set
92 {
93 moLargeChange = value;
94 Invalidate();
95 }
96 }
97
98 [EditorBrowsable(EditorBrowsableState.Always), Browsable( true ), DefaultValue( false ), Category( " Behavior " ), Description
99
100 ( " SmallChange " )]
101 public int SmallChange
102 {
103 get { return moSmallChange; }
104 set
105 {
106 moSmallChange = value;
107 Invalidate();
108 }
109 }
110
111 [EditorBrowsable(EditorBrowsableState.Always), Browsable( true ), DefaultValue( false ), Category( " Behavior " ), Description( " Minimum " )]
112 public int Minimum
113 {
114 get { return moMinimum; }
115 set
116 {
117 moMinimum = value;
118 Invalidate();
119 }
120 }
121
122 [EditorBrowsable(EditorBrowsableState.Always), Browsable( true ), DefaultValue( false ), Category( " Behavior " ), Description( " Maximum " )]
123 public int Maximum
124 {
125 get { return moMaximum; }
126 set
127 {
128 moMaximum = value;
129 Invalidate();
130 }
131 }
132
133 [EditorBrowsable(EditorBrowsableState.Always), Browsable( true ), DefaultValue( false ), Category( " Behavior " ), Description( " Value " )]
134 public int Value
135 {
136 get { return moValue; }
137 set
138 {
139 moValue = value;
140
141 int nTrackHeight = ( this .Height - (UpArrowImage.Height + DownArrowImage.Height));
142 float fThumbHeight = (( float )LargeChange / ( float )Maximum) * nTrackHeight;
143 int nThumbHeight = ( int )fThumbHeight;
144
145 if (nThumbHeight > nTrackHeight)
146 {
147 nThumbHeight = nTrackHeight;
148 fThumbHeight = nTrackHeight;
149 }
150 if (nThumbHeight < 56 )
151 {
152 nThumbHeight = 56 ;
153 fThumbHeight = 56 ;
154 }
155
156
157 int nPixelRange = nTrackHeight - nThumbHeight;
158 int nRealRange = (Maximum - Minimum) - LargeChange;
159 float fPerc = 0.0f ;
160 if (nRealRange != 0 )
161 {
162 fPerc = ( float )moValue / ( float )nRealRange;
163
164 }
165
166 float fTop = fPerc * nPixelRange;
167 moThumbTop = ( int )fTop;
168
169
170 Invalidate();
171 }
172 }
173
174 [EditorBrowsable(EditorBrowsableState.Always), Browsable( true ), DefaultValue( false ), Category( " Skin " ), Description( " Channel Color " )]
175 public Color ChannelColor
176 {
177 get { return moChannelColor; }
178 set { moChannelColor = value; }
179 }
180
181 [EditorBrowsable(EditorBrowsableState.Always), Browsable( true ), DefaultValue( false ), Category( " Skin " ), Description( " Up Arrow
182
183 Graphic " )]
184 public Image UpArrowImage
185 {
186 get { return moUpArrowImage; }
187 set { moUpArrowImage = value; }
188 }
189
190 [EditorBrowsable(EditorBrowsableState.Always), Browsable( true ), DefaultValue( false ), Category( " Skin " ), Description( " Up Arrow
191
192 Graphic " )]
193 public Image DownArrowImage
194 {
195 get { return moDownArrowImage; }
196 set { moDownArrowImage = value; }
197 }
198
199
200
201
202 [EditorBrowsable(EditorBrowsableState.Always), Browsable( true ), DefaultValue( false ), Category( " Skin " ), Description( " Up Arrow
203
204 Graphic " )]
205 public Image ThumbBottomImage
206 {
207 get { return moThumbBottomImage; }
208 set { moThumbBottomImage = value; }
209 }
210
211
212
213 [EditorBrowsable(EditorBrowsableState.Always), Browsable( true ), DefaultValue( false ), Category( " Skin " ), Description( " Up Arrow
214
215 Graphic " )]
216 public Image ThumbMiddleImage
217 {
218 get { return moThumbMiddleImage; }
219 set { moThumbMiddleImage = value; }
220 }
221
222 protected override void OnPaint(PaintEventArgs e)
223 {
224
225 e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
226
227 if (UpArrowImage != null )
228 {
229 e.Graphics.DrawImage(UpArrowImage, new Rectangle( new Point( 0 , 0 ), new Size( this .Width, UpArrowImage.Height)));
230 }
231
232 Brush oBrush = new SolidBrush(moChannelColor);
233 Brush oWhiteBrush = new SolidBrush(Color.FromArgb( 255 , 255 , 255 ));
234
235 e.Graphics.FillRectangle(oWhiteBrush, new Rectangle( 0 , UpArrowImage.Height, 1 , ( this .Height - DownArrowImage.Height)));
236 e.Graphics.FillRectangle(oWhiteBrush, new Rectangle( this .Width - 1 , UpArrowImage.Height, 1 , ( this .Height -
237
238 DownArrowImage.Height)));
239
240
241 e.Graphics.DrawImage(ThumbBottomImage, new Rectangle( 0 , UpArrowImage.Height, this .Width, ( this .Height - DownArrowImage.Height)));
242
243 int nTrackHeight = ( this .Height - (UpArrowImage.Height + DownArrowImage.Height));
244 float fThumbHeight = (( float )LargeChange / ( float )Maximum) * nTrackHeight;
245 int nThumbHeight = ( int )fThumbHeight;
246
247 if (nThumbHeight > nTrackHeight)
248 {
249 nThumbHeight = nTrackHeight;
250 fThumbHeight = nTrackHeight;
251 }
252
253 if (nThumbHeight < 56 )
254 {
255 nThumbHeight = 56 ;
256 fThumbHeight = 56 ;
257 }
258
259
260 int nTop = moThumbTop; // 0
261 nTop += UpArrowImage.Height; // 9px
262
263
264 e.Graphics.DrawImage(ThumbMiddleImage, new Rectangle( 0 , nTop, this .Width, ThumbMiddleImage.Height));
265
266
267
268 if (DownArrowImage != null )
269 {
270 e.Graphics.DrawImage(DownArrowImage, new Rectangle( new Point( 0 , ( this .Height - DownArrowImage.Height)), new Size( this .Width,
271
272 DownArrowImage.Height)));
273 }
274
275 }
276
277
278 public override bool AutoSize
279 {
280 get
281 {
282 return base .AutoSize;
283 }
284 set
285 {
286 base .AutoSize = value;
287 if ( base .AutoSize)
288 {
289 this .Width = moUpArrowImage.Width;
290 }
291 }
292 }
293
294 private void InitializeComponent()
295 {
296 this .SuspendLayout();
297
298 this .Name = " CustomScrollbar " ;
299 this .MouseDown += new System.Windows.Forms.MouseEventHandler( this .CustomScrollbar_MouseDown);
300 this .MouseMove += new System.Windows.Forms.MouseEventHandler( this .CustomScrollbar_MouseMove);
301 this .MouseUp += new System.Windows.Forms.MouseEventHandler( this .CustomScrollbar_MouseUp);
302 this .ResumeLayout( false );
303
304 }
305
306 private void CustomScrollbar_MouseDown( object sender, MouseEventArgs e)
307 {
308 Point ptPoint = this .PointToClient(Cursor.Position);
309 int nTrackHeight = ( this .Height - (UpArrowImage.Height + DownArrowImage.Height));
310 float fThumbHeight = (( float )LargeChange / ( float )Maximum) * nTrackHeight;
311 int nThumbHeight = ( int )fThumbHeight;
312
313 if (nThumbHeight > nTrackHeight)
314 {
315 nThumbHeight = nTrackHeight;
316 fThumbHeight = nTrackHeight;
317 }
318 if (nThumbHeight < 56 )
319 {
320 nThumbHeight = 56 ;
321 fThumbHeight = 56 ;
322 }
323
324 int nTop = moThumbTop;
325 nTop += UpArrowImage.Height;
326
327
328 Rectangle thumbrect = new Rectangle( new Point( 1 , nTop), new Size(ThumbMiddleImage.Width, nThumbHeight));
329 if (thumbrect.Contains(ptPoint))
330 {
331
332
333 nClickPoint = (ptPoint.Y - nTop);
334
335 this .moThumbDown = true ;
336 }
337
338 Rectangle uparrowrect = new Rectangle( new Point( 1 , 0 ), new Size(UpArrowImage.Width, UpArrowImage.Height));
339 if (uparrowrect.Contains(ptPoint))
340 {
341
342 int nRealRange = (Maximum - Minimum) - LargeChange;
343 int nPixelRange = (nTrackHeight - nThumbHeight);
344 if (nRealRange > 0 )
345 {
346 if (nPixelRange > 0 )
347 {
348 if ((moThumbTop - SmallChange) < 0 )
349 moThumbTop = 0 ;
350 else
351 moThumbTop -= SmallChange;
352
353
354 float fPerc = ( float )moThumbTop / ( float )nPixelRange;
355 float fValue = fPerc * (Maximum - LargeChange);
356
357 moValue = ( int )fValue;
358 Debug.WriteLine(moValue.ToString());
359
360 if (ValueChanged != null )
361 ValueChanged( this , new EventArgs());
362
363 if (Scroll != null )
364 Scroll( this , new EventArgs());
365
366 Invalidate();
367 }
368 }
369 }
370
371 Rectangle downarrowrect = new Rectangle( new Point( 1 , UpArrowImage.Height + nTrackHeight), new Size(UpArrowImage.Width,
372
373 UpArrowImage.Height));
374 if (downarrowrect.Contains(ptPoint))
375 {
376 int nRealRange = (Maximum - Minimum) - LargeChange;
377 int nPixelRange = (nTrackHeight - nThumbHeight);
378 if (nRealRange > 0 )
379 {
380 if (nPixelRange > 0 )
381 {
382 if ((moThumbTop + SmallChange) > nPixelRange)
383 moThumbTop = nPixelRange;
384 else
385 moThumbTop += SmallChange;
386
387
388 float fPerc = ( float )moThumbTop / ( float )nPixelRange;
389 float fValue = fPerc * (Maximum - LargeChange);
390
391 moValue = ( int )fValue;
392 Debug.WriteLine(moValue.ToString());
393
394 if (ValueChanged != null )
395 ValueChanged( this , new EventArgs());
396
397 if (Scroll != null )
398 Scroll( this , new EventArgs());
399
400 Invalidate();
401 }
402 }
403 }
404 }
405
406 private void CustomScrollbar_MouseUp( object sender, MouseEventArgs e)
407 {
408 this .moThumbDown = false ;
409 this .moThumbDragging = false ;
410 }
411
412 private void MoveThumb( int y)
413 {
414 int nRealRange = Maximum - Minimum;
415 int nTrackHeight = ( this .Height - (UpArrowImage.Height + DownArrowImage.Height));
416 float fThumbHeight = (( float )LargeChange / ( float )Maximum) * nTrackHeight;
417 int nThumbHeight = ( int )fThumbHeight;
418
419 if (nThumbHeight > nTrackHeight)
420 {
421 nThumbHeight = nTrackHeight;
422 fThumbHeight = nTrackHeight;
423 }
424 if (nThumbHeight < 56 )
425 {
426 nThumbHeight = 56 ;
427 fThumbHeight = 56 ;
428 }
429
430 int nSpot = nClickPoint;
431
432 int nPixelRange = (nTrackHeight - nThumbHeight);
433 if (moThumbDown && nRealRange > 0 )
434 {
435 if (nPixelRange > 0 )
436 {
437 int nNewThumbTop = y - (UpArrowImage.Height + nSpot);
438
439 if (nNewThumbTop < 0 )
440 {
441 moThumbTop = nNewThumbTop = 0 ;
442 }
443 else if (nNewThumbTop > nPixelRange)
444 {
445 moThumbTop = nNewThumbTop = nPixelRange;
446 }
447 else
448 {
449 moThumbTop = y - (UpArrowImage.Height + nSpot);
450 }
451
452
453 float fPerc = ( float )moThumbTop / ( float )nPixelRange;
454 float fValue = fPerc * (Maximum - LargeChange);
455 moValue = ( int )fValue;
456 Debug.WriteLine(moValue.ToString());
457
458 Application.DoEvents();
459
460 Invalidate();
461 }
462 }
463 }
464
465 private void CustomScrollbar_MouseMove( object sender, MouseEventArgs e)
466 {
467 if (moThumbDown == true )
468 {
469 this .moThumbDragging = true ;
470 }
471
472 if ( this .moThumbDragging)
473 {
474
475 MoveThumb(e.Y);
476 }
477
478 if (ValueChanged != null )
479 ValueChanged( this , new EventArgs());
480
481 if (Scroll != null )
482 Scroll( this , new EventArgs());
483 }
484
485 }
486
487 internal class ScrollbarControlDesigner : System.Windows.Forms.Design.ControlDesigner
488 {
489
490
491
492 public override SelectionRules SelectionRules
493 {
494 get
495 {
496 SelectionRules selectionRules = base .SelectionRules;
497 PropertyDescriptor propDescriptor = TypeDescriptor.GetProperties( this .Component)[ " AutoSize " ];
498 if (propDescriptor != null )
499 {
500 bool autoSize = ( bool )propDescriptor.GetValue( this .Component);
501 if (autoSize)
502 {
503 selectionRules = SelectionRules.Visible | SelectionRules.Moveable | SelectionRules.BottomSizeable |
504
505 SelectionRules.TopSizeable;
506 }
507 else
508 {
509 selectionRules = SelectionRules.Visible | SelectionRules.AllSizeable | SelectionRules.Moveable;
510 }
511 }
512 return selectionRules;
513 }
514 }
515 }
516 }
目前只想简单实现滚动条中上箭头/下箭头/滑道/拖动块的重写,所以以上代码中OnPaint函数里的部分内容被我注释了,好了,这个滚动条控件已经做好了,一个控件而已,你应该会使用它,我就不罗嗦了。
接下来就是怎么用它来控制ListBox的内容滚动的问题了,这需要调用API函数来实现,同时又不能设置ListBox无滚动条,因为ListBox没有滚动条也就没有滚动的事件可捕获,那就达不到滚动的效果了。
在你的窗体里拖一个listbox控件和一个上边我们制作好的用户控件,分明命名为listBox和customScrollbar1,
然后往listBox中随便多弄写内容,使之出现滚动条即可。调整customScrollbar1的位置使之覆盖在listBox的滚动条上,呵呵,这方法不错吧?
然后我们定义一下Win32API,代码如下:


public class Win32API
{
[StructLayout(LayoutKind.Sequential)]
public struct tagSCROLLINFO
{
public uint cbSize;
public uint fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
}
public enum fnBar
{
SB_HORZ = 0,
SB_VERT = 1,
SB_CTL = 2
}
public enum fMask
{
SIF_ALL,
SIF_DISABLENOSCROLL = 0X0010,
SIF_PAGE = 0X0002,
SIF_POS = 0X0004,
SIF_RANGE = 0X0001,
SIF_TRACKPOS = 0X0008
}
public static int MakeLong(short lowPart, short highPart)
{
return (int)(((ushort)lowPart) | (uint)(highPart << 16));
}
public const int SB_THUMBTRACK = 5;
public const int WM_HSCROLL = 0x114;
public const int WM_VSCROLL = 0x115;
[DllImport("user32.dll", EntryPoint = "GetScrollInfo")]
public static extern bool GetScrollInfo(IntPtr hwnd, int fnBar, ref SCROLLINFO lpsi);
[DllImport("user32.dll", EntryPoint = "SetScrollInfo")]
public static extern int SetScrollInfo(IntPtr hwnd, int fnBar, [In] ref SCROLLINFO lpsi, bool fRedraw);
[DllImport("User32.dll", CharSet = CharSet.Auto, EntryPoint = "SendMessage")]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, long wParam,int lParam);
}
public struct SCROLLINFO
{
public uint cbSize;
public uint fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
}
enum ScrollInfoMask
{
SIF_RANGE = 0x1,
SIF_PAGE = 0x2,
SIF_POS = 0x4,
SIF_DISABLENOSCROLL = 0x8,
SIF_TRACKPOS = 0x10,
SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS
}
enum ScrollBarDirection
{
SB_HORZ = 0,
SB_VERT = 1,
SB_CTL = 2,
SB_BOTH = 3
}
public SCROLLINFO tvImageListScrollInfo
{
get
{
SCROLLINFO si = new SCROLLINFO();
si.cbSize = (uint)Marshal.SizeOf(si);
si.fMask = (int)(ScrollInfoMask.SIF_DISABLENOSCROLL | ScrollInfoMask.SIF_ALL);
Win32API.GetScrollInfo(listBox.Handle, (int)ScrollBarDirection.SB_VERT, ref si);
return si;
}
}
//当鼠标滚动时,设置该滚动条
private void SetImageListScroll()
{
SCROLLINFO info = tvImageListScrollInfo;
if (info.nMax > 0)
{
int pos = info.nPos - 1;
if (pos >= 0)
{
customScrollbar1.Value = pos;
}
}
}
定义customScrollbar1的滚动事件函数customScrollbar1_Scroll,实现在滚动条滚动的同时发消息给listBox使之同步滚动


private void customScrollbar1_Scroll(object sender, EventArgs m)
{
//当滚动条滚动时,通知控件也跟着滚动吧。。。
SCROLLINFO info = tvImageListScrollInfo;
info.nPos = customScrollbar1.Value;
Win32API.SetScrollInfo(listBox.Handle, (int)ScrollBarDirection.SB_VERT, ref info, true);
Win32API.PostMessage(listBox.Handle, Win32API.WM_VSCROLL, Win32API.MakeLong((short)Win32API.SB_THUMBTRACK, (short)(info.nPos)), 0);
}
感谢博友Yellowyu在滚动控制方面给予的帮助。
调整了一下滚动条重绘所用到的图片的尺寸,看起来效果好多了!!!