在本人的上一篇隨筆<<高仿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在滾動控制方面給予的幫助
調整了一下滾動條重繪所用到的圖片的尺寸,看起來效果好多了!!!