|
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。2 n0 A7 V0 V ]) K
& r3 e7 p6 O0 E( a" A$ {5 p
% ]8 k: Z- Q' U
+ }: c& I' h- g我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。
: q" `3 T% D3 N) ^+ P& o* e. n1 Z' K# _! V那么,靠边隐藏功能到底是怎么实现的了?# l3 Z) C( }1 B. P6 ^) \
[& j L% l5 |8 ]4 S7 I: [
一.靠边隐藏的原理
' G9 Q0 T( {2 u5 d0 }8 k! \; y6 ^6 m+ W+ _4 `; j$ Q9 {
靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:
, J o. ^; O' ~0 O! L: L1 `方案说明如下:1 E0 J3 f0 F/ b+ R r
(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。
3 }' `! W) v' u3 K5 d6 H f(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。" l4 ?$ w; ]2 {. s4 Q
(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:
( h* ]1 F' i, p a. 当鼠标再度离开窗体区域,则又隐藏窗体。
$ F* B d7 e9 [3 w) t: n4 K" D& H b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。8 i0 z ]4 W8 |& Z1 p5 z
* E+ ^3 {" E/ F9 B* g j
二.具体实现过程1 e- {! g0 P( b" O- d# D z
9 r1 ~) h( Z% |- k1.基本元素定义# J1 b+ J* \9 M
首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:7 d; X2 Z8 z0 e( {$ S c2 f
/// <summary> / B/ f1 _0 k$ Y
/// 靠边隐藏的类型。 $ z5 O* p2 s1 d; u
/// </summary>
$ q" N* o8 N2 G: Rpublic enum DockHideType
- \3 C, S! ]5 X2 H! U0 ?{ 1 H) g0 x l# C) x7 N! v4 ?. B
/// <summary>
, {. D! V. l9 m/ ~% r+ R /// 不隐藏 * m Z1 A8 l8 x8 U% L! r! U- F
/// </summary> + z7 U3 p W/ x9 j% x v
None = 0, ( C9 {' A) V( n# n$ w, ~2 a
/// <summary> + H$ t1 C3 V6 K ]
/// 靠上边沿隐藏
$ P3 ~' E) @ U8 N /// </summary>
: s P% r o/ o1 p Top,
# o }+ q$ r6 E: q) d3 Z+ f /// <summary>
; \% d# M4 A/ s& q: o0 N /// 靠左边沿隐藏 - q, P6 X7 l$ ^( Z" p
/// </summary> ! o; H6 A+ ] }4 ^
Left, ! d( k& C$ [1 I- @$ D
/// <summary> ; U1 y$ `; z+ I8 {' ]
/// 靠右边沿隐藏
! d" V$ U& n" t5 r /// </summary> 0 J! m1 u: l. H8 |) h5 _6 Z$ k; H
Right
* d; o/ g* j. W; _}# w a/ F+ M' o
复制代码& K3 U& h2 E9 P2 G, N
# _# [8 |5 r' @; F8 r# U1 j, @其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:
/ B+ ~5 t* S& X) ~. t/// <summary>
$ _6 v1 }; m- Y$ Z% R /// 窗体的显示或隐藏状态
3 y. }* x: Y/ m8 |* ~ /// </summary> ' j' P! S* o% @! F9 ?3 }6 w
public enum FormDockHideStatus
! S% c- G+ A- S; j { , T' y& c" ^6 ~+ V
/// <summary> 5 v/ y- V5 v) ?3 Z; e3 k
/// 已隐藏
3 x8 A ~* H8 [8 z7 V+ l /// </summary> + C6 O3 w' c: m w
Hide = 0, ! o0 ]( N% j. j2 a N
4 x2 b; I& i- | /// <summary>
) M+ x' V9 }1 u/ t } /// 准备隐藏 9 D7 X" i$ ?7 `6 D* s! E
/// </summary> 6 _' T { v) g6 ^' Y2 F2 l
ReadyToHide,
7 } M9 z( e5 L3 T8 P
3 R O5 R' D- i: d /// <summary>
' a/ o4 y) @8 o1 G, `) b, V% J& o /// 正常显示 - S7 B5 t: r, Z. V( p1 N
/// </summary>
) c* A. k* d2 ~3 e8 ? ShowNormally
: b5 @2 x' ?, J k }
/ W. B2 A r6 A% a复制代码
! L% Z p7 G# G0 U( g0 p4 F
8 ~* k; E( @6 Q& b) ~, r+ ~: H7 H2 }
2.判断是否达到隐藏条件
7 T4 C+ Z5 r9 N$ ]' r# K
- t8 r6 M/ \9 l0 B: T% t很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。
r8 T$ _- [6 T/ E) Mprivate void dockedForm_LocationChanged(object sender, EventArgs e) & a, [0 O/ a, c$ G/ b* N
{
% i) y# U9 l! u h$ ? this.ComputeDockHideType();
- G" R e) F* L$ U9 D$ o9 c7 X if (!this.IsOrg) % W6 c4 h8 H' `6 A0 G9 ?
{ ; P! x, ]3 o1 R: T1 ?7 g
this.lastBoard = this.dockedForm.Bounds; 8 Z2 N v+ _6 l7 L2 g4 L
this.IsOrg = true; / n) ]' l. `8 b; x7 n5 Y% B7 \
}
5 l; p: o/ n! b' y4 B8 A }
' \% M/ d6 a' C( t8 h1 c6 Q1 s
1 Z+ h# J2 X- i4 E/ e. H6 K /// <summary> 0 y) d% B. c: N5 A% W& n& t% G, L
/// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。
! s# O1 B$ p, g* m# d0 ~ /// </summary>
" c' F0 p: O4 G private void ComputeDockHideType() , n( `3 G. K2 l5 M: Z0 Q
{
9 V8 s& @ B$ r6 | if (this.dockedForm.Top <= 0) 9 V2 ?: J7 L- P R, k7 W7 N {% t
{
" @$ ]# p$ Y, [1 Z* t* @" y+ Q this.dockHideType = DockHideType.Top; 4 h5 K2 Q; H; y7 g: w. p
if (this.dockedForm.Bounds.Contains(Cursor.Position))
( ^% P7 Y% ?1 Y. N% R" {2 j: _3 T7 W {
4 o: \$ Z, x+ O% q. W! W5 _ this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
# x! \* E2 _: i1 k return; 4 j( T/ o# ?4 s+ i' |
}
! p; w9 S$ W) G this.formDockHideStatus = FormDockHideStatus.Hide;
a1 q0 V: l1 Q: c return;
% a2 g+ w3 x+ Y }
* }; c+ B# n8 p- A6 E0 |0 T+ h else
" V& Q# [. H0 I% ]# O% L2 d {
$ s$ c7 s% U2 q; d; G if (this.dockedForm.Left <= 0)
5 I5 F# E# u( _' ]% L/ q { 3 f. I! O% t$ a8 v$ j+ J
this.dockHideType = DockHideType.Left;
3 ]# F- U2 A& g" C( i if (this.dockedForm.Bounds.Contains(Cursor.Position)) ! S4 |3 E7 I7 s1 `
{ * ]0 W( l5 y+ \5 m3 E
this.formDockHideStatus = FormDockHideStatus.ReadyToHide; / l8 Z/ b3 i3 T, |" Y5 x
return;
" T, O( n# d" g! t }
( ?2 b k3 c: k& ]) r( j* {+ A3 m; {# c this.formDockHideStatus = FormDockHideStaus.Hide;
% c5 |" J5 p. c. q% a+ V; k4 G return; # Y0 e, p! O# J7 v! \& f) ` v
} ( E: m9 y( c) A: _. m
else
% \+ g8 f% r9 X- R+ w# o; I$ u* F { + g% t/ }3 |# t8 z$ f. X5 g8 x+ X( B
if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width) * L/ C+ R- K, \1 l; ?0 m7 v" w$ M
{ ( e. F' n' B* \1 C& B
this.dockHideType = DockHideType.None; $ p, A/ V1 c4 J& _- V4 j
this.formDockHideStatus = FormDockHideStatus.ShowNormally; 2 m& s. m; E) {9 O5 b7 J) U
return;
( U% P! K/ E, _- b$ Z6 W } 1 q& |3 S5 t# Y+ R( a1 S, {
this.dockHideType = DockHideType.Right;
& V. w8 n: Y7 \ if (this.dockedForm.Bounds.Contains(Cursor.Position)) # q# f/ J q6 M2 [& @2 K! d
{
$ R0 w k$ j" h, Q this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
# }3 y4 F4 Y* i4 A" V4 Z& p return; 9 z; H0 p2 N! p8 I3 `8 B
}
, l+ w! e; O( H! C this.formDockHideStatus = FormDockHideStatus.Hide; 2 g T1 b0 g0 z
return;
- y' L2 {' o x! L# j+ L6 g5 i } # ^1 s- _& |+ O: X% J$ w& L
} / H% x+ v1 G2 b
}0 T8 p+ u( A: A+ ~
复制代码
) G+ \/ _3 m! X5 j0 E
8 { {) v: x0 A
" |' e; ^/ J0 Q上面的代码主要体现了以下几个要点:/ `, l( ]+ W* \3 R. h9 L: C9 g
; q2 v7 x* u5 b0 ^# ~(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。
/ `, _1 O# n: L m6 u3 f r(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。
! g3 M8 {' L# r, P; _# m(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。- P a. T) u4 J: f) u. }
详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。7 M0 M& h- k! T# H$ Z6 a( ^; w9 n
6 s- t4 S7 X) g4 ?& z* O3.定时检测满足/退出隐藏条件' X3 h3 g. t5 o' ^0 D
( d! s, A% w0 G4 W9 g1 D我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。
' `* B+ o, ^. |/ v' u$ v! S$ {/// <summary> # N5 W4 K. \; w% H; g" B# B; m
/// 定时器循环判断。 2 E8 r2 M; D0 ~
/// </summary>
2 s6 W, ~/ a* ~' Z! w private void CheckPosTimer_Tick(object sender, EventArgs e)
, G" a- J) W" m/ L& O {//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外)
( N- u3 W: K- _# N if (this.dockedForm.Bounds.Contains(Cursor.Position)) 9 U4 m, r: E0 U5 X r
{ if (this.dockHideType!= DockHideType.Top) + s7 B3 ^0 F% J1 ~- E
{ + }/ ^ V& v2 o5 T
if (this.dockHideType!= DockHideType.Left)
! o& A, O0 L+ t9 E2 q4 r9 i {
5 R& _. o$ s' l W. F* b if (this.dockHideType!= DockHideType.Right)
, j, H$ z3 K* k* d {
+ S* W5 B s, |1 k return;
( r" t; F% ~' F/ Y' H } 1 B1 n/ D6 L# k) I
if (this.formDockHideStatus == FormDockHideStatus.Hide)
/ g6 ?$ d; [( h6 D' V# f7 ^ {
+ P X; S! f C( n this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y);
0 W9 \; N7 G6 b return;
+ G& G$ U1 ^, S3 c) S1 h4 o } $ s, s3 c' c' `. t b2 H
}
. y7 H5 v; T9 h; m0 @ else
0 W1 e% s) q% c$ A6 Z8 S {
0 d. N, n1 w% l1 y5 H6 l/ } u, B if (this.formDockHideStatus == FormDockHideStatus.Hide) 8 C, n/ ~9 K- ?4 v2 G* S Z5 S5 Y
{ 4 ^* s4 _- H; p k7 _ v, @4 b9 }% X$ Q' Y
this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y);
" U: u6 _/ m. J/ ?- t return; # P! Z0 l9 T2 E
} : Y0 Y! w3 ?4 b/ b
}
& u) H& n5 a+ n* A1 J } 7 a6 W- {: q: m" d& a. t; L
else
3 a: B ~. G9 ] { 8 m2 l& x9 ^/ r# ]. g
if (this.formDockHideStatus == FormDockHideStatus.Hide) 9 p# J4 P) `# i Q; w" d/ ^! r
{ 3 | H( q% u" K' f- y
this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0); $ ?: N" X7 S/ D! l8 n2 P7 J
return;
3 c: ~9 a) ^, b B8 H( v- i" Q X } 9 g% ]8 p0 [* x
}
Z4 ?4 Q% D$ o# |+ p }
5 o& e4 ?( Z, V5 R0 [1 B# F2 m0 z) @+ c else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。
; G7 @7 ]) x4 F2 J- o# e { switch (this.dockHideType)
& k3 W) D$ N, b7 | {
7 L8 G1 W* ?7 m# R case DockHideType.None: " q) K+ w" |# Z5 U+ v
{
$ d! Q @1 d0 ?. Y if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally &&
# f @) `: T9 p- x! x (this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height))
5 ?7 z# B! v. S {
( G4 g8 q, x) ] _ this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height);
, Z* n4 e% x4 l2 P } ' k) i2 D: c. a" T% E
break; + n: p6 E, J) j$ w N3 _" ^* d& ]
}
4 P% O- B F3 x, e case DockHideType.Top: 7 k' l$ S d5 h2 P( T# U
{ ) h( f3 N* l" ?: a Z% B' z2 q
this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1); ( i0 i) f2 A0 A. O
return; s3 C% a% G) d8 }
} : k5 T5 ~( a: U# V2 k
case DockHideType.Left:
9 F& I% r5 S9 F6 O" y5 o. C1 V {
6 T4 k% ?9 X, W2 l& c6 z/ A this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y);
5 D0 { R/ L K. e return; 1 \& o3 m0 a* x9 d* j
} , b& ]* ^# A) D5 W7 m
default:
3 g+ w, r }' a* q0 b% F {
! `2 E U N0 V7 y3 o7 D5 ?, f0 l' o if (anchorStyles2 != DockHideType.Right)
# a2 b, Z# J- z- a/ w {
% I1 N/ U5 ?0 c% ^8 j T% S return; % q# R- `8 \- W
} ( x! y2 @6 W- J* T0 ?; d2 o9 J
this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y); 2 }6 K9 R6 P; |
return;
' T& x: c8 _1 c2 a+ i/ Y } 7 I z$ v1 }0 F* a
} $ A3 h' p! X, L9 `$ w, ~0 L) E
}
2 J8 \) V6 A1 m; b: T# H# {+ _ }1 d: V2 m& u2 K; x$ z" ]5 @- ]
复制代码' H( J: I! r6 z
! d4 v+ C, @: t0 f9 M. u2 a6 z" l6 `* p% ~3 H! A- m( J# }
(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。5 H2 n- G. U. t
(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。
w2 D; i/ s- o9 t$ i) B(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。
7 k! U) Y" H9 n/ _( w" f2 D4 `: R+ H3 d3 d
三.如何使用AutoDocker组件
/ L( h, \6 }! y% q" B1 q
8 R4 o4 Q4 n2 ]& f! [! AAutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:
" i7 D& K- F% s7 x3 Z从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码: f+ Z% T2 n7 {: ^0 u& y* X
this.autoDocker1.Initialize(this);
! ]2 E7 z6 g$ p! A( W( N5 ?9 v复制代码0 C( i" B4 P- L: ~4 K$ x4 t
这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~
1 W% o# N( y J! x' s T; G* h1 I
. I2 g) F8 z6 a! o0 S8 b |
|