C# WinForm 滾動條換膚
在看過這篇文章後,如果您有什麼好的意見和建議,請在下面留言。
先看一下效果圖:
上圖實現的是一個仿FoxMail的換膚控制元件的截圖,其中實現了TreeView、Panel、DataGridView和ListBox的滾動條的換膚,下面介紹一下滾動條換膚的原來。
在網上搜了一下,滾動條換膚一般有兩種方法實現。第一種方式比較變態,直接攔截系統繪製滾動條的訊息,將繪製過程接管過來,這種方式顯然不是C#的強項,不過網上已經有了C++的實現版本,如果對C++比較熟悉的話,可以研究一下,如果哪位用空改成C#的版本,希望可以給我發一份。
C++版本的滾動條換膚示例程式碼下載:
第二種方法相對來說比較簡單,就是將自己繪製的滾動條覆蓋在控制元件的滾動條上面,然後讓自己繪製的滾動條和控制元件自身的滾動條進行聯動就可以了。關於如何自繪滾動條,網上應該可以找得到,我這裡就不詳細說了,只提供一個滾動條繪製的原始碼給大家下載。
上面介紹了滾動條繪製需要處理的一些問題,下面的就是本文的重點,如何讓自定義滾動條和控制元件的滾動條進行聯動。下面我用DataGridview的滾動條的例子來說明。
其它地方我就不多說了,程式碼裡面寫得很明白,關鍵的水平和垂直滾動條的相關引數的說明給大家講解一下,這個引數的演算法絕對是原創,並且可以跟系統自帶的滾動條高度的同步:
this._hScrollBarEx.Maximum = 需要滾動的區域的寬度;(包括顯示區域和未顯示的滾動區域的寬度,注意不包括行標題列)
this._hScrollBarEx.LargeChange = _hScrollBarEx.Maximum / _hScrollBarEx.Width + 顯示區域的寬度(可顯示的區域的寬度,注意不包括行標題列);
this._vScrollBarEx.Maximum = 顯示區域包含的資料的行數 + _vScrollBarEx.Minimum + _vScrollBarEx.LargeChange(預設值為100) - 1;
public
{
private int _displayRowCount = 0;
private int _displayWidth = 0;
private ScrollBarEx.HScrollBarEx _hScrollBarEx;
private ScrollBarEx.VScrollBarEx _vScrollBarEx;
public DataGridViewEx():base()
{
_hScrollBarEx = new HScrollBarEx();
_vScrollBarEx = new VScrollBarEx();
this.Controls.Add(_hScrollBarEx);
this.Controls.Add(_vScrollBarEx);
base.SetStyle(ControlStyles.UserPaint |ControlStyles.AllPaintingInWmPaint |ControlStyles.OptimizedDoubleBuffer, true);
this.HorizontalScrollBar.VisibleChanged += new EventHandler(ScrollBar_VisibleChanged);
this.VerticalScrollBar.VisibleChanged += new EventHandler(ScrollBar_VisibleChanged);
this.SizeChanged += new EventHandler(ScrollBar_VisibleChanged);
this._vScrollBarEx.ValueChanged += new EventHandler(VScrollBarEx_ValueChanged);
this._hScrollBarEx.ValueChanged += new EventHandler(HScrollBarEx_ValueChanged);
this.Scroll += new ScrollEventHandler(DataGridViewEx_Scroll);
this.ColumnHeadersHeightChanged += new EventHandler(ScrollBar_VisibleChanged);
this.ColumnWidthChanged += new DataGridViewColumnEventHandler(ScrollBar_VisibleChanged);
this.RowHeadersWidthChanged += new EventHandler(ScrollBar_VisibleChanged);
this.RowHeightChanged += new DataGridViewRowEventHandler(ScrollBar_VisibleChanged);
this.RowsAdded += new DataGridViewRowsAddedEventHandler(ScrollBar_VisibleChanged);
this.RowsRemoved += new DataGridViewRowsRemovedEventHandler(ScrollBar_VisibleChanged);
this.ColumnAdded += new DataGridViewColumnEventHandler(ScrollBar_VisibleChanged);
this.ColumnRemoved += new DataGridViewColumnEventHandler(ScrollBar_VisibleChanged);
this.DataSourceChanged += new EventHandler(ScrollBar_VisibleChanged);
SetScrollBarEx();
}
private void VScrollBarEx_ValueChanged(object sender, EventArgs e)
{
if (!this.dgvScroll)
{
this.FirstDisplayedScrollingRowIndex = _vScrollBarEx.Value;
Application.DoEvents();
}
}
private void HScrollBarEx_ValueChanged(object sender, EventArgs e)
{
if (!this.dgvScroll)
{
this.HorizontalScrollingOffset = _hScrollBarEx.Value;
GetDisplayWidth();
Application.DoEvents();
}
}
private void DataGridViewEx_Scroll(object sender, ScrollEventArgs e)
{
this.dgvScroll = true;
if (e.ScrollOrientation == ScrollOrientation.VerticalScroll)
{
_vScrollBarEx.Value = this.FirstDisplayedScrollingRowIndex;
}
else
{
_hScrollBarEx.Value = this.HorizontalScrollingOffset;
}
this.dgvScroll = false;
}
private void ScrollBar_VisibleChanged(object sender, EventArgs e)
{
SetScrollBarEx();
}
private void SetScrollBarEx()
{
if (this.VerticalScrollBar.Visible)
{
_vScrollBarEx.Visible = true;
_vScrollBarEx.Location = new Point(this.DisplayRectangle.Width, 0);
this.VerticalScrollBar.SendToBack();
_vScrollBarEx.Height = this.DisplayRectangle.Height;
GetDisplayRowCount();
}
else
{
_vScrollBarEx.Visible = false;
}
if (this.HorizontalScrollBar.Visible)
{
_hScrollBarEx.Visible = true;
_hScrollBarEx.Location = new Point(0, this.DisplayRectangle.Height);
this.HorizontalScrollBar.SendToBack();
_hScrollBarEx.Width = this.DisplayRectangle.Width ;
GetDisplayWidth();
_hScrollBarEx.Value = this.HorizontalScrollingOffset;
}
else
{
_hScrollBarEx.Visible = false;
}
}
public int GetDisplayWidth()
{
int width = 0;
for (int i = 0; i < this.Columns.Count; i++)
{
width += this.Columns[i].Width;
}
_displayWidth = width;
this._hScrollBarEx.Maximum = width;
this._hScrollBarEx.LargeChange = _hScrollBarEx.Maximum / _hScrollBarEx.Width + this.DisplayRectangle.Width - this.RowHeadersWidth;
return width;
}
public int GetDisplayRowCount()
{
int j = 0;
int height = 0;
int i = this.FirstDisplayedScrollingRowIndex;
if (i < 0)
{
i = 0;
}
for (; i < this.Rows.Count; i++)
{
height += this.Rows[i].Height;
if (height < this.DisplayRectangle.Height - this.ColumnHeadersHeight)
{
j++;
}
else
{
break;
}
}
j = this.Rows.Count - j;
if (j < 0)
{
j = 0;
}
if (_displayRowCount != j)
{
_displayRowCount = j;
_vScrollBarEx.Maximum = j + _vScrollBarEx.Minimum + _vScrollBarEx.LargeChange - 1;
if (this.FirstDisplayedScrollingRowIndex < 0)
{
_vScrollBarEx.Value = 0;
}
else if (this.FirstDisplayedScrollingRowIndex > _vScrollBarEx.Maximum)
{
_vScrollBarEx.Value = _vScrollBarEx.Maximum;
}
else
{
_vScrollBarEx.Value = this.FirstDisplayedScrollingRowIndex;
}
}
return j;
}
}
DataGridview的滾動條不是由系統進行繪製的,所以滾動條的很多引數都是可以取到的,但是像Listbox或者Textbox這樣的控制元件,滾動條的資訊很多都不好獲取,這個時候,就要使用API函式來進行處理了,但整個滾動條的覆蓋方案還是可以參照上面的來進行,只是把一些引數的獲取和設定改由API函式來完成就行了,當然在一些細節的地方會有所不同。
特別要注意的是下面兩句:
我原來以為只要用SetScrollInfo設定控制元件的滾動條引數後控制元件就會自動滾動到指定的位置,但實際結果卻不是這樣的,必須還要呼叫PostMessage傳送一個讓控制元件滾動的訊息控制元件才會滾動。
Win32API.SetScrollInfo(tvImageList.Handle, (int)ScrollBarDirection.SB_VERT, ref info, true);
Win32API.PostMessage(tvImageList.Handle, Win32API.WM_VSCROLL, Win32API.MakeLong((short)Win32API.SB_THUMBTRACK, (short)(info.nPos)), 0);