阅读全文:http://www.cckan.net/forum.php?mod=viewthread&tid=258
C#仿QQ皮肤-实现原理系列文章导航
http://www.cnblogs.com/sufei/archive/2010/03/10/1682847.html
前面的文章说了不少的费话,从这一节开始说点实际点的东西,皮肤的实现原理,我通过下面几步跟大家一点一点的抛吧.
第一步 皮肤的分类,一共有两个大类,我是根据实现的对象不同来分的,感觉这样可能会容易理解一下,因为窗体皮肤和控件皮肤还是有一些不同的,窗体的属性和控件的属性不一至,所 以在实现上有少许的差别,在这里我把他们分成两类;
1.窗体皮肤,
2.控件皮肤
1.窗体皮肤
窗体皮肤不用多说大家都 知道就是窗体的皮肤,当然这里面我还细分为窗体,和用户控件两种,也就是From和UserControl
在这里我们实现以下几个窗体和用户控件
1. 基窗体FormBase与基用户控件FormBase1的实现
2. 基窗体FunctionFormBase的实现
3. 主窗体MainForm和Main的实现
4. 用户控件EnterUserControl的实现
5. 皮肤控件窗体SkinForm的实现
6. Windows消息提示框窗体MessageBoxForm的实现
7. 常用用户控件EnterFrom1和窗体EntryForm的实现
一个一个的来说明吧,大家先下载一下最新的皮肤 这样方便理解
第一个,基窗体FormBase与基用户控件FormBase1
这 是两个基窗体吧,应该可以这样讲吧,FormBase是所有以上窗体的基窗体,也就是说上面的窗体创建都要继承这个窗体,而FormBase1 是做什么的呢,呵呵 ,其实他跟FormBase的功能 是完全一样的,只有一点区别就是FormBase是窗体,而FormBase1 是用户控件,实现的方法也是一样的
基窗体的实现是很简单的
第二个,基窗体FunctionFormBase的实现
FunctionFormBase这是一个窗体,跟FormBase的功能是一样的,是其它窗体的基窗体,像皮肤控制窗体SkinForm这样的窗体是要继承它的。
任何一个基窗体里都是用来重绘和拦截Windows消息的,做过自定义滚动条的朋友应该都 知道 什么是Windows消息,.net没有提共控件的直接Scroll,我们只能通过拦截Windows消息来处理了,和处理APIHook来解决问题了,Windows消息一般要处理这样几个VSCROLL,WM_HITTEST,WM_NCMOUSEMOVE等
说到滚动条多说几句,在处理滚动条的时候一般都 要处理这样几个方法
1.第一个是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();
}
}
其它的跟这个基本上差不多,都 是先画个自己的滚动条,然后通过Api把系统的隐藏,把自己的加上,并且让他们产生连动;
用到的Api介绍一下
代码
[DllImport(
"
user32.dll
"
)]
[
return
: MarshalAs(UnmanagedType.Bool)]
public
static
extern
bool
GetScrollInfo(IntPtr hwnd,
int
fnBar,
ref
SCROLLINFO lpsi);
[DllImport(
"
user32.dll
"
)]
public
static
extern
int
SetScrollInfo(IntPtr hwnd,
int
fnBar, [In]
ref
SCROLLINFO
lpsi,
bool
fRedraw);
关于这两个Api的介绍如下
GetScrollInfo
该函数找到滚动条的参数,包括滚动条位置的最小值、最大值,页面大小,滚动按钮的 位置,
函数原型:BOOL GetScrolllnfo(HWND hWnd,int fnBar,LPSCROLLINFO lpsi);
参数:
hWnd:滚动条控制或有标准滚动条的窗体句柄,由fnBar参数确定。
fnBar:指定待找回滚动条参数的类型,此参数可以为如下值,其值含义:
SB_CTL:找回滚动条控制参数。其中参数hwnd一定是处理滚动条控制的句柄。
SB_HORZ:找回所指定窗体的标准水平滚动条参数。
SB_VERT:找回所指定窗体的标准垂直滚动条参数。
lpsi:指向SCROLLINFO结构。在调用Getscrolllofo函数之前,设置SCROLLINFO结构中cbSize成员以标识结构 大小,设置成员fMask以说明待找回的滚动条参数。在运行之前,函数复制结构中适当的成员所指定的参数。
成员fMask可以是如下值:
SIF_PAGE:复制滚动页码到由lpsi指向的SCROLLINFO结构的nPage成员中。
SIF_POS:复制滚动位置到由lpsi指向的SCROLLINFO结构的nPos成员中。
GetScrollInfo-相关资料
SIF_RANGE:复制滚动范围到由lpsi指向的SCROLLINFO结构的nMin和nMax成员中。
SIF_TRACKPOS:复制当前滚动盒跟踪位置到由nTrackPos指向的SCROLLINFO结构的 nPage成员中。
返回值:如果函数找到任何一个值,那么返回值为非零;如果函数没有找到任何值,那么返回值为零;
注意:Getscrolllnfo函数尽管WM_HSCROLL和WM_VSCROLL指出了滚动条位置消息,却仅提供了16位数据,而函数 SetScrollnfo和GetScrollnfo则提供了32位的滚动条数据。因而,当应用程序在处理WM_HSCROLL或 WM_VSCROLL时,要获得32位滚动条位置的数据时, 则要调用Getscrolllnfo函数。 在WM_HSCROLL或WM_VSCROLL消息中SB_THUMBTRACK通告过程中,为了获得32位的滚动盒位置,需要调用 GetScrolllnfo函数以得到结构SCROLLINFO成员fMask中的SCROLLINFO值。函数返回在结构SCROLLINFO成员nTrackPos中指出的滚动盒跟踪位置的值。这将允许当用户移动滚动盒时能得到其位置。
SetScrollInfo
函数功能:该函数设置滚动条参数,包括滚动位置的最大值和最小值,页面大小,滚动按钮的位置。如被请求,函数也可以重画滚动条。
函数原型:int SetScrollInfo(HWND hWnd;int fnBar,LPSCROLLINFO lpsi,BOOL fRedraw);
参数:
hWnd:滚动条控制或带标准滚动条的窗体句柄,由fnBar参数决定。
fnBar:指定被设定参数的滚动条的类型。这个参数可以是下面值,含义如下:
SB_CTL:设置滚动条控制。而参数hwnd必须是滚动条控制的句柄。
SB_HORZ:设置所给定的窗体上标准水平滚动条参数。
SB_VERT:设置所给定的窗体上标准垂直滚动条参数。
IPBI:指向SCROLLINFO结构。在调用SetScrognfo之前,设置 SCROLLINFO结构中cbSize成员以标识结构大小,设置成员fMask以说明待设置的滚动条参数,并且在适当的成员中制定新的参数值。成员 fMask可以为下面所列复合值,含义如下:
SIF_DfSABLENOSCROLL:如果滚动条的新参数使其为没必要,则使滚动条无效而不再移动它。
SIF_PAGE:设置滚动页码值到由Ipsi指向的SCROLLINFO结构的nPage成员中。
SIF_POS:设置滚动位置值到由lpsi指向的SCROLLINFO结构的nPos成员中。
SIF_RANGE:设置滚动范围值到由lpsl指向的SCROLLINFO结构的nMin和nMax成员中。
fRedraw:指定滚动条是否重画以反映滚动条的变化。如果这个参数为TRUE,滚动条将被重画,否则不被重画。
返回值:返回值是滚动盒的当前位置。
注意:SetScrolllnfo函数执行任务是检查SCROLLINFO结构中由成员 nPage和nPos值的范围。成员uPage值必须从0到nMax- nMin+1,成员nPos必须是在nMin和nMax-nMax-max(nPage C1,0)之间的指定值。如果任何一个值超过了这个范围,函数将在指定范围内为它设置一个值。
第二个,是鼠标事件我在这里处理了三个,参考 一下吧
代码
//
鼠标按下
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
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());
}
第三个 就是MoveThumb方法了,看方法体
代码
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();
}
}
}
滚动条先到这里吧,具体的到这个控件制作时再具体的说,
我们接着看
第三个 主窗体MainForm和Main窗体
见名思意MainForm,主窗体,这是一个主窗体,是一般化的窗体,只有菜单和皮肤两项菜单,不带有换底纹的功能界面如下
Main窗体就更好说了,就是我前面所有用到 的窗体如下

有点长了,本来打算今天 把框架说完了,看来是不行了,下次接着来吧,下面的四个下次接着来吧,这次多少写的滚动条的代码 有些多了,哎。
完吧!