在本人的上一篇隨筆<<高仿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的代碼吧
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Diagnostics;
namespace Winamp
{
[Designer(typeof(ScrollbarControlDesigner))]
public partial class CustomScrollbar : UserControl
{
protected Color moChannelColor = Color.Empty;
protected Image moUpArrowImage = null;//上箭頭
//protected Image moUpArrowImage_Over = null;
//protected Image moUpArrowImage_Down = null;
protected Image moDownArrowImage = null;//下箭頭
//protected Image moDownArrowImage_Over = null;
//protected Image moDownArrowImage_Down = null;
protected Image moThumbArrowImage = null;
protected Image moThumbTopImage = null;
protected Image moThumbTopSpanImage = null;
protected Image moThumbBottomImage = null;
protected Image moThumbBottomSpanImage = null;
protected Image moThumbMiddleImage = null;
protected int moLargeChange = 10;
protected int moSmallChange = 1;
protected int moMinimum = 0;
protected int moMaximum = 100;
protected int moValue = 0;
private int nClickPoint;
protected int moThumbTop = 0;
protected bool moAutoSize = false;
private bool moThumbDown = false;
private bool moThumbDragging = false;
public new event EventHandler Scroll = null;
public event EventHandler ValueChanged = null;
private int GetThumbHeight()
{
int nTrackHeight = (this.Height -
(UpArrowImage.Height + DownArrowImage.Height));
float fThumbHeight = ((float)LargeChange /
(float)Maximum) * nTrackHeight;
int nThumbHeight = (int)fThumbHeight;
if (nThumbHeight > nTrackHeight)
{
nThumbHeight = nTrackHeight;
fThumbHeight = nTrackHeight;
}
if (nThumbHeight < 56)
{
nThumbHeight = 56;
fThumbHeight = 56;
}
return nThumbHeight;
}
public CustomScrollbar()
{
InitializeComponent();
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.AllPaintingInWmPaint,
true);
SetStyle(ControlStyles.DoubleBuffer, true);
moChannelColor = Color.FromArgb(51, 166, 3);
UpArrowImage = BASSSkin.uparrow;//上箭頭
DownArrowImage = BASSSkin.downarrow;//下肩頭
ThumbBottomImage = BASSSkin.ThumbBottom;
ThumbMiddleImage = BASSSkin.ThumbMiddle;
this.Width = UpArrowImage.Width;//18px
base.MinimumSize = new Size(UpArrowImage.Width,
UpArrowImage.Height + DownArrowImage.Height + GetThumbHeight());
}
[EditorBrowsable(EditorBrowsableState.Always),
Browsable(true), DefaultValue(false), Category("Behavior"),
Description("LargeChange")]
public int LargeChange
{
get { return moLargeChange; }
set
{
moLargeChange = value;
Invalidate();
}
}
[EditorBrowsable(EditorBrowsableState.Always),
Browsable(true), DefaultValue(false), Category("Behavior"),
Description("SmallChange")]
public int SmallChange
{
get { return moSmallChange; }
set
{
moSmallChange = value;
Invalidate();
}
}
[EditorBrowsable(EditorBrowsableState.Always),
Browsable(true), DefaultValue(false), Category("Behavior"),
Description("Minimum")]
public int Minimum
{
get { return moMinimum; }
set
{
moMinimum = value;
Invalidate();
}
}
[EditorBrowsable(EditorBrowsableState.Always),
Browsable(true), DefaultValue(false), Category("Behavior"),
Description("Maximum")]
public int Maximum
{
get { return moMaximum; }
set
{
moMaximum = value;
Invalidate();
}
}
[EditorBrowsable(EditorBrowsableState.Always),
Browsable(true), DefaultValue(false), Category("Behavior"),
Description("Value")]
public int Value
{
get { return moValue; }
set
{
moValue = value;
int nTrackHeight = (this.Height -
(UpArrowImage.Height + DownArrowImage.Height));
float fThumbHeight = ((float)LargeChange
/ (float)Maximum) * nTrackHeight;
int nThumbHeight = (int)fThumbHeight;
if (nThumbHeight > nTrackHeight)
{
nThumbHeight = nTrackHeight;
fThumbHeight = nTrackHeight;
}
if (nThumbHeight < 56)
{
nThumbHeight = 56;
fThumbHeight = 56;
}
//figure out value
int nPixelRange = nTrackHeight -
nThumbHeight;
int nRealRange = (Maximum - Minimum) -
LargeChange;
float fPerc = 0.0f;
if (nRealRange != 0)
{
fPerc = (float)moValue /
(float)nRealRange;
}
float fTop = fPerc * nPixelRange;
moThumbTop = (int)fTop;
Invalidate();
}
}
[EditorBrowsable(EditorBrowsableState.Always),
Browsable(true), DefaultValue(false), Category("Skin"), Description
("Channel Color")]
public Color ChannelColor
{
get { return moChannelColor; }
set { moChannelColor = value; }
}
[EditorBrowsable(EditorBrowsableState.Always),
Browsable(true), DefaultValue(false), Category("Skin"), Description("Up
Arrow Graphic")]
public Image UpArrowImage
{
get { return moUpArrowImage; }
set { moUpArrowImage = value; }
}
[EditorBrowsable(EditorBrowsableState.Always),
Browsable(true), DefaultValue(false), Category("Skin"), Description("Up
Arrow Graphic")]
public Image DownArrowImage
{
get { return moDownArrowImage; }
set { moDownArrowImage = value; }
}
[EditorBrowsable(EditorBrowsableState.Always),
Browsable(true), DefaultValue(false), Category("Skin"), Description("Up
Arrow Graphic")]
public Image ThumbBottomImage
{
get { return moThumbBottomImage; }
set { moThumbBottomImage = value; }
}
[EditorBrowsable(EditorBrowsableState.Always),
Browsable(true), DefaultValue(false), Category("Skin"), Description("Up
Arrow Graphic")]
public Image ThumbMiddleImage
{
get { return moThumbMiddleImage; }
set { moThumbMiddleImage = value; }
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
if (UpArrowImage != null)
{
e.Graphics.DrawImage(UpArrowImage, new
Rectangle(new Point(0, 0), new Size(this.Width, UpArrowImage.Height)));
}
Brush oBrush = new SolidBrush(moChannelColor);
Brush oWhiteBrush = new SolidBrush
(Color.FromArgb(255, 255, 255));
// 函數名: rectangle
//功 能: 畫一個矩形
//用 法: void far rectangle(int left, int top,
int right, int bottom);
//draw channel left and right border colors
e.Graphics.FillRectangle(oWhiteBrush, new
Rectangle(0, UpArrowImage.Height, 1, (this.Height -
DownArrowImage.Height)));
e.Graphics.FillRectangle(oWhiteBrush, new
Rectangle(this.Width - 1, UpArrowImage.Height, 1, (this.Height -
DownArrowImage.Height)));
//draw channel
//e.Graphics.FillRectangle(oBrush, new
Rectangle(1, UpArrowImage.Height, this.Width-2, (this.Height-
DownArrowImage.Height)));
e.Graphics.DrawImage(ThumbBottomImage, new
Rectangle(0, UpArrowImage.Height, this.Width, (this.Height -
DownArrowImage.Height)));
//draw thumb
int nTrackHeight = (this.Height -
(UpArrowImage.Height + DownArrowImage.Height));
float fThumbHeight = ((float)LargeChange /
(float)Maximum) * nTrackHeight;
int nThumbHeight = (int)fThumbHeight;
if (nThumbHeight > nTrackHeight)
{
nThumbHeight = nTrackHeight;
fThumbHeight = nTrackHeight;
}
//MessageBox.Show(nThumbHeight.ToString());
if (nThumbHeight < 56)
{
nThumbHeight = 56;
fThumbHeight = 56;
}
//Debug.WriteLine(nThumbHeight.ToString());
//float fSpanHeight = (fThumbHeight -
(ThumbMiddleImage.Height + ThumbTopImage.Height +
ThumbBottomImage.Height)) / 2.0f;
//int nSpanHeight = (int)fSpanHeight;
int nTop = moThumbTop;//0
nTop += UpArrowImage.Height;//9px
//draw top畫上面的按鈕
//e.Graphics.DrawImage(ThumbTopImage, new
Rectangle(0, nTop, this.Width, ThumbTopImage.Height));
//nTop += ThumbTopImage.Height;//10px
//draw top span
//Rectangle rect = new Rectangle(1, nTop,
this.Width - 2, nSpanHeight);
//e.Graphics.DrawImage(ThumbTopSpanImage, 1.0f,
(float)nTop, (float)this.Width-2.0f, (float) fSpanHeight*2);
// nTop += nSpanHeight;//11px
//draw middle
e.Graphics.DrawImage(ThumbMiddleImage, new
Rectangle(0, nTop, this.Width, ThumbMiddleImage.Height));
//nTop += ThumbMiddleImage.Height;
//draw top span
//rect = new Rectangle(1, nTop, this.Width - 2,
nSpanHeight*2);
//e.Graphics.DrawImage(ThumbBottomSpanImage,
rect);
//nTop += nSpanHeight;
//draw bottom
//e.Graphics.DrawImage(ThumbBottomImage, new
Rectangle(1, nTop, this.Width - 2, nSpanHeight));
if (DownArrowImage != null)
{
e.Graphics.DrawImage(DownArrowImage, new
Rectangle(new Point(0, (this.Height - DownArrowImage.Height)), new
Size(this.Width, DownArrowImage.Height)));
}
}
public override bool AutoSize
{
get
{
return base.AutoSize;
}
set
{
base.AutoSize = value;
if (base.AutoSize)
{
this.Width =
moUpArrowImage.Width;
}
}
}
private void InitializeComponent()
{
this.SuspendLayout();
//
// CustomScrollbar
//
this.Name = "CustomScrollbar";
this.MouseDown += new
System.Windows.Forms.MouseEventHandler(this.CustomScrollbar_MouseDown);
this.MouseMove += new
System.Windows.Forms.MouseEventHandler(this.CustomScrollbar_MouseMove);
this.MouseUp += new
System.Windows.Forms.MouseEventHandler(this.CustomScrollbar_MouseUp);
this.ResumeLayout(false);
}
private void CustomScrollbar_MouseDown(object sender,
MouseEventArgs e)
{
Point ptPoint = this.PointToClient
(Cursor.Position);
int nTrackHeight = (this.Height -
(UpArrowImage.Height + DownArrowImage.Height));
float fThumbHeight = ((float)LargeChange /
(float)Maximum) * nTrackHeight;
int nThumbHeight = (int)fThumbHeight;
if (nThumbHeight > nTrackHeight)
{
nThumbHeight = nTrackHeight;
fThumbHeight = nTrackHeight;
}
if (nThumbHeight < 56)
{
nThumbHeight = 56;
fThumbHeight = 56;
}
int nTop = moThumbTop;
nTop += UpArrowImage.Height;
Rectangle thumbrect = new Rectangle(new Point(1,
nTop), new Size(ThumbMiddleImage.Width, nThumbHeight));
if (thumbrect.Contains(ptPoint))
{
//hit the thumb
nClickPoint = (ptPoint.Y - nTop);
//MessageBox.Show(Convert.ToString
((ptPoint.Y - nTop)));
this.moThumbDown = true;
}
Rectangle uparrowrect = new Rectangle(new Point
(1, 0), new Size(UpArrowImage.Width, UpArrowImage.Height));
if (uparrowrect.Contains(ptPoint))
{
int nRealRange = (Maximum - Minimum) -
LargeChange;
int nPixelRange = (nTrackHeight -
nThumbHeight);
if (nRealRange > 0)
{
if (nPixelRange > 0)
{
if ((moThumbTop -
SmallChange) < 0)
moThumbTop = 0;
else
moThumbTop -=
SmallChange;
//figure out value
float fPerc = (float)
moThumbTop / (float)nPixelRange;
float fValue = fPerc *
(Maximum - LargeChange);
moValue = (int)fValue;
Debug.WriteLine
(moValue.ToString());
if (ValueChanged !=
null)
ValueChanged
(this, new EventArgs());
if (Scroll != null)
Scroll(this, new
EventArgs());
Invalidate();
}
}
}
Rectangle downarrowrect = new Rectangle(new
Point(1, UpArrowImage.Height + nTrackHeight), new Size
(UpArrowImage.Width, UpArrowImage.Height));
if (downarrowrect.Contains(ptPoint))
{
int nRealRange = (Maximum - Minimum) - LargeChange;
int nPixelRange = (nTrackHeight - nThumbHeight);
if (nRealRange > 0)
{
if (nPixelRange > 0)
{
if ((moThumbTop + SmallChange) > nPixelRange)
moThumbTop = nPixelRange;
else
moThumbTop += SmallChange;
//figure out value
float fPerc = (float)moThumbTop / (float)nPixelRange;
float fValue = fPerc * (Maximum - LargeChange);
moValue = (int)fValue;
Debug.WriteLine(moValue.ToString());
if (ValueChanged != null)
ValueChanged(this, new EventArgs());
if (Scroll != null)
Scroll(this, new EventArgs());
Invalidate();
}
}
}
}
private void CustomScrollbar_MouseUp(object sender, MouseEventArgs e)
{
this.moThumbDown = false;
this.moThumbDragging = false;
}
private void MoveThumb(int y)
{
int nRealRange = Maximum - Minimum;
int nTrackHeight = (this.Height - (UpArrowImage.Height + DownArrowImage.Height));
float fThumbHeight = ((float)LargeChange / (float)Maximum) * nTrackHeight;
int nThumbHeight = (int)fThumbHeight;
if (nThumbHeight > nTrackHeight)
{
nThumbHeight = nTrackHeight;
fThumbHeight = nTrackHeight;
}
if (nThumbHeight < 56)
{
nThumbHeight = 56;
fThumbHeight = 56;
}
int nSpot = nClickPoint;
int nPixelRange = (nTrackHeight - nThumbHeight);
if (moThumbDown && nRealRange > 0)
{
if (nPixelRange > 0)
{
int nNewThumbTop = y - (UpArrowImage.Height + nSpot);
if (nNewThumbTop < 0)
{
moThumbTop = nNewThumbTop = 0;
}
else if (nNewThumbTop > nPixelRange)
{
moThumbTop = nNewThumbTop = nPixelRange;
}
else
{
moThumbTop = y - (UpArrowImage.Height + nSpot);
}
//figure out value
float fPerc = (float)moThumbTop / (float)nPixelRange;
float fValue = fPerc * (Maximum - LargeChange);
moValue = (int)fValue;
Debug.WriteLine(moValue.ToString());
Application.DoEvents();
Invalidate();
}
}
}
private void CustomScrollbar_MouseMove(object sender, MouseEventArgs e)
{
if (moThumbDown == true)
{
this.moThumbDragging = true;
}
if (this.moThumbDragging)
{
MoveThumb(e.Y);
}
if (ValueChanged != null)
ValueChanged(this, new EventArgs());
if (Scroll != null)
Scroll(this, new EventArgs());
}
}
internal class ScrollbarControlDesigner : System.Windows.Forms.Design.ControlDesigner
{
public override SelectionRules SelectionRules
{
get
{
SelectionRules selectionRules = base.SelectionRules;
PropertyDescriptor propDescriptor = TypeDescriptor.GetProperties(this.Component)["AutoSize"];
if (propDescriptor != null)
{
bool autoSize = (bool)propDescriptor.GetValue(this.Component);
if (autoSize)
{
selectionRules = SelectionRules.Visible | SelectionRules.Moveable |
SelectionRules.BottomSizeable | SelectionRules.TopSizeable;
}
else
{
selectionRules =
SelectionRules.Visible | SelectionRules.AllSizeable |
SelectionRules.Moveable;
}
}
return selectionRules;
}
}
}
}
目前只想簡單實現滾動條中上箭頭/下箭頭/滑道/拖動塊的重寫,所以以上代碼 中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在滾動控制方面給予的幫助

調整了一下滾動條重繪所用到的圖片的尺寸,看起來效果好多了!!!