|
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。& r/ p) }6 i; l* k' F
/ Z; S; @& W* U3 Z r$ H8 B" _0 m
. V% m0 z. x6 S8 N
3 P f, t f6 D9 T' \: {, }+ y3 a
我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。
' U# g8 \1 x# d, J那么,靠边隐藏功能到底是怎么实现的了?
9 j: [& y. Q5 G; E9 L6 m+ V+ ], ?
4 q+ x3 Y0 w6 [一.靠边隐藏的原理
7 c6 k* j$ m! b* \. }5 M4 j8 k! w
靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:3 F7 A# f+ |- o! |4 w
方案说明如下:
# {' K$ \ d( I! e2 f(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。
7 W6 G6 M/ b6 U. ~(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。
+ C. U( J9 ^3 M! S, h0 R(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:/ \5 c. @0 }; t0 ?! [- }
a. 当鼠标再度离开窗体区域,则又隐藏窗体。: `' H0 j6 _3 z' S5 w4 D# n
b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。; h" J4 L. I# U1 e$ E" ^1 ]& r
: \, }* m% l, u( g6 ?; B二.具体实现过程
4 v; S! y! q. w9 G* i+ y
' D: m+ k% _; _2 }- D7 _# H5 F1.基本元素定义6 q4 m! }7 S' e! R( P% J+ U; I
首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:6 l: M* z; {) l% T( R: X
/// <summary>
2 U. j" H! {& u/ m# A' s+ X/ V/// 靠边隐藏的类型。 1 c: P8 M5 u, q. M
/// </summary> - }4 s$ Q3 G+ l7 b% f7 ?
public enum DockHideType
* f+ x: k+ U% `{
6 G" r& d- L: ]# \ /// <summary> ! D# U4 U, ]& X: M3 Q. c( l5 ^
/// 不隐藏
# W: d' I& Y$ N% ~! a0 ^ /// </summary> ( r! T- e% r L* a+ W$ _
None = 0,
/ m; K8 F0 ]* |, {0 d /// <summary> ' d2 D" ]; g3 E7 i- z, e
/// 靠上边沿隐藏
3 y3 }0 P s& j* j /// </summary>
: _, r( x0 U* E/ o1 t Top, % {' k' ?- n( H8 t1 l; i* W, |
/// <summary> : V+ [2 u- T( ]5 z7 a/ i( \
/// 靠左边沿隐藏 7 l* R8 N- Z# M6 A6 g. O4 D
/// </summary>
4 Q* L& j7 T6 I& c$ _! @% R$ Y Left,
[9 x, C$ d, h! z /// <summary>
: p$ [% q6 z! Y" L /// 靠右边沿隐藏 / J9 W4 G5 W2 N( O
/// </summary>
8 p- e! u' N) ]9 F5 x) k Right 7 G) B; u4 l/ x; Q. f
}
. p9 f0 w6 {& g( |7 c6 e复制代码
: f) P* ]' H2 h/ F! @
- X# V" w$ a0 n# q% l其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:
, m- X. \+ e0 P4 U& E4 i; {/// <summary>
. _, @& q/ M3 n- M# W1 ~; J0 | /// 窗体的显示或隐藏状态
- b% t# b4 k# ^( e& z /// </summary>
9 c- C3 @" E# h- N) E; I. B public enum FormDockHideStatus
, }7 U2 V0 d, s3 c6 U% E {
1 B% c% K3 v9 K1 r5 T9 ~; [* f /// <summary> & ^% {7 Y# j% _" I2 `
/// 已隐藏
) f2 v; G p2 b1 A /// </summary> 7 z$ ]3 U1 {. v3 ^; d! u
Hide = 0, 5 X" |& b, V( s3 Z+ X: c8 @
$ H4 Z- r6 V: V& P /// <summary>
. ~/ _' a. o5 Q' B9 l' _ /// 准备隐藏
5 {6 m0 s( j: O; O- Q! x /// </summary>
2 }+ U z0 U* `8 e0 v \3 [ ReadyToHide,
7 t9 K$ Z8 k, D' v; F5 y) e Y' h" t: o# v! F" \
/// <summary> # X, |1 `8 t1 H2 {
/// 正常显示
# i# U1 ~% d, k% U /// </summary> $ k% ^4 l- G2 x
ShowNormally
4 `) [6 v/ W6 B9 t }& f; \4 S1 m5 v0 ^3 r
复制代码
8 a. o* G5 L g1 ?4 W4 g. g% k2 \+ J' N3 N: y: Q: G8 [" e
; Z; W5 N. m- M- s) c* v& p) u5 k( ?
2.判断是否达到隐藏条件
8 @. q; x' N4 \0 y# B2 O8 M y3 i; i! p' A" w; X8 R
很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。
, r Q# f9 z( J: [private void dockedForm_LocationChanged(object sender, EventArgs e)
3 }- k% X0 Y+ Y+ a4 @- a { K4 x/ e4 Z w* v; M- }# ?. L
this.ComputeDockHideType();
' i$ S- G3 s. L" V1 z+ I if (!this.IsOrg) : `* @: _ l# ?0 d' L. k# Z
{ 2 H5 N4 W6 X0 d* E y' J
this.lastBoard = this.dockedForm.Bounds;
) I5 y& E5 s0 n( A8 Y this.IsOrg = true;
9 i7 h- b- B/ ^7 y0 p } 9 _8 E! c8 O+ U- Y7 }
}
2 J+ `0 n/ x- j L( C0 u
+ U" b4 w9 B: F) z. h L' g /// <summary>
2 v0 X9 Q. K3 ]' o7 k /// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。
6 G0 o+ M1 }: }" y: Y5 P" N9 a# v /// </summary> . ? o" Z: J4 s. ], s9 J
private void ComputeDockHideType() " F& I/ `, [2 `8 @3 E
{ . d3 p0 [' k8 J: Q R8 }
if (this.dockedForm.Top <= 0) 0 `( a1 d5 x9 g4 w
{
/ ^4 H. I8 G/ J8 q this.dockHideType = DockHideType.Top;
3 _, e0 B9 h: @1 f( f2 ] if (this.dockedForm.Bounds.Contains(Cursor.Position))
$ j* z( e/ M) v7 B/ \6 j/ m2 S p% } { 2 G; ~' M b% r
this.formDockHideStatus = FormDockHideStatus.ReadyToHide; 5 j! o, x+ [1 F% {
return; 5 [' X8 M* }. ] z8 |) }9 W8 A, R
} $ H) i/ J% T2 j n/ Y& f; ]
this.formDockHideStatus = FormDockHideStatus.Hide;
$ Z& f# ^' m3 r1 u& Q) o( F return; ) `: g/ }0 \# P6 S" F, E
} 9 A' m1 @2 L1 _/ M3 g
else % F% w, R1 L* G) i3 w; `$ A
{ ( O& w: ~- V% \
if (this.dockedForm.Left <= 0) 7 P* P& D$ V7 v
{ 3 M# O5 \3 M! s; B! ]! u" a
this.dockHideType = DockHideType.Left; ; x9 f( j# r3 I9 D. {- h
if (this.dockedForm.Bounds.Contains(Cursor.Position)) " ?3 H4 G( _7 t/ D+ r7 q# I8 n& _
{
' N3 R; h% g; J$ [ this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
* O3 ^/ u, O- |* T7 M2 A4 u return;
) J; o( `$ i/ v O- q+ f5 }3 e } $ N, v* [8 [' P! B/ k/ _7 E% W
this.formDockHideStatus = FormDockHideStaus.Hide; 4 O. O$ l, c3 t
return; % H, j2 b! p$ u! [: w, r
}
7 f" ]4 n& K/ K& Z, O, n6 | else ) _$ B ]9 c: [- O; A2 m
{ 2 U7 m9 ]7 i5 z- D. N V$ m6 `" k% i/ c
if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width)
. h h1 b, `) z9 v& h { 5 {. \9 [. k6 E. w1 |# }
this.dockHideType = DockHideType.None; ; s4 E4 @) H% I6 I7 ` I" z! M
this.formDockHideStatus = FormDockHideStatus.ShowNormally; , V4 U$ f0 k6 Z9 y+ H$ t/ x- ^
return; : e! h0 }# I' j2 d
} & {) w7 a. N3 n6 v: K1 x
this.dockHideType = DockHideType.Right;
7 E9 g4 }' k, y if (this.dockedForm.Bounds.Contains(Cursor.Position)) 6 i- i& p$ G0 V$ Q
{
8 x% i+ I* v; A6 q: E this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
1 F! Q9 \ x$ I1 V return;
$ y$ p. M: ]5 ?2 j; N }
$ |+ v- j p6 W4 w7 B9 v9 U this.formDockHideStatus = FormDockHideStatus.Hide; 8 [" C4 c0 z/ b0 X
return;
+ q$ F! C2 h; b+ K t# n4 L } . l7 N b% A& t+ a8 C4 u8 i
}
1 c+ A+ P- r+ |6 {4 {5 ?, n }1 \5 B+ { x1 t- r2 U3 @5 ~' n3 j
复制代码
9 s# w! _& V/ |- j
7 C. o7 f6 R4 l4 m, H' ~& L, S1 k4 i4 P6 x6 W; F( @
上面的代码主要体现了以下几个要点:
) }, n. e9 ]# _ ?+ p+ G8 |0 v2 ? _* f3 D7 C
(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。
g6 R5 \$ r( e; w7 @( Y(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。7 N& c' D! U' S6 g
(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。
- D* a: S; h2 |! \7 b详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。8 g0 F6 u$ m( r |/ _2 L
! ~- V3 J9 b5 v) m; ^
3.定时检测满足/退出隐藏条件& @/ O" p8 h6 _6 o
9 _4 {& a$ K9 q2 c8 C我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。3 q% s# _: Y- Y% U/ m1 W
/// <summary>
( U7 w, H4 u' ^( q+ P- ]$ g0 j$ J /// 定时器循环判断。
H4 Z4 I$ H. V$ V+ X# o /// </summary> : k8 ?% P' _$ U/ g. |' w: {
private void CheckPosTimer_Tick(object sender, EventArgs e) 7 X+ z7 |# ?1 t; F$ I$ ^
{//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外) : {, \* M2 r- g. ?4 K
if (this.dockedForm.Bounds.Contains(Cursor.Position)) 6 D# c6 ]$ R8 s0 Y$ r0 c
{ if (this.dockHideType!= DockHideType.Top)
! ^: B6 J( f0 X6 w) V# s {
' ]4 y, r, O! e/ a" B) M if (this.dockHideType!= DockHideType.Left) & f) {: ^8 n! _" a
{ ! F9 B y+ k+ b0 L* @! X% V
if (this.dockHideType!= DockHideType.Right)
8 R- r+ y% F2 s1 a {
) b1 Y: a" `5 C4 T+ T# T8 p return; , U; U0 |. \ J9 b
}
/ f% I& ~2 X p& c, O7 F8 B if (this.formDockHideStatus == FormDockHideStatus.Hide)
8 P1 |2 y! q3 D: J( Q# q {
$ j0 y8 b( N" Q this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y);
: {9 n( u; r: a- n0 y return; ! h) ~3 [* W5 ]& e
} + R6 t& q! w) z9 M
}
& r C! m6 h! |( a" A" s0 @2 B else 1 w `& D2 o) ~" l
{
- p' w1 i: |4 _% w3 Z if (this.formDockHideStatus == FormDockHideStatus.Hide)
5 {7 y% q( X8 } {
0 r4 {: S& @. | this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y); # V$ X6 ^2 D0 t; @# r" q3 n4 Q
return; " l/ J9 [/ [. M5 W( [3 I& `
}
) v3 Y4 g$ Z: h$ v* {* Z3 U) Y$ d3 ?( U } % | ^, g$ ~' P Z
}
# i# ]0 _; B/ z. j5 S- x4 V else
$ |$ Q% H! P: K8 c& C. \ { + _' ~3 r7 _# Z- J! ~
if (this.formDockHideStatus == FormDockHideStatus.Hide) ) I! A5 \" {! L% }7 V' `! t. p
{ 6 r- F, _6 w$ y* i8 E
this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0); / a$ U' @- n8 y% ~' U: c) s
return;
! a; [8 [7 ~8 T B }
9 y% Q9 ^( T7 S }
7 d! ^- s7 d& C7 D$ I }
0 p" r4 ~# } m" a" Y9 s else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。
+ W" t+ O' F2 I0 A { switch (this.dockHideType) 2 }- y6 h+ ~* C/ d* v
{ 6 H! S% g, B/ b) x2 \; R
case DockHideType.None:
! `9 V* Z) E( L: g { 1 l7 x O; k& ~! [: W3 P
if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally && * D G/ X. j2 n: w- v9 C2 a7 Z
(this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height))
/ [ Y" ^$ g' N; S9 \1 h, Q* m {
# j' y+ a6 D1 R) S1 M9 h this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height); 1 [) M* j4 V ?) Y3 U O
}
' P4 L% K- n9 @4 [ break; + [" I& y" v; G9 B) Q. i( E( S
} 2 {& z. L+ ?: n/ l, r
case DockHideType.Top:
5 T C7 [" V% H' A1 B4 E) r { 0 q, S7 |, V W: K
this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1);
6 {) r4 T3 Q: U return;
; \5 q1 t! x1 A } , a# | v, I- t! Z$ N
case DockHideType.Left:
( L. f( V2 n! |4 Z( V- r { % m7 w* t5 n3 o+ _# \
this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y); 8 T2 x# r0 o0 O, x# X
return;
( ]. h/ E+ ?3 x } + I1 e7 K; S3 h) A) C
default: 7 e9 N3 H) j5 D% T6 C. K
{ / R$ S5 z; f3 G7 N# L: M L; h; S
if (anchorStyles2 != DockHideType.Right) 6 ^: l- m' H4 R
{ # b9 q$ p" l( l/ I( p% n& T
return;
) r- o8 P# q2 v }
2 [, T" O- I" Z. u: d* } this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y); # w! [- V) @3 X' v0 O
return; " Y' w- L' ?) y1 z* x6 u
} " g3 _3 |' @2 M1 @" n, B
}
. J; M! e+ T) @& _* h- Z. }& A) p }
) d$ |# H' E8 j. a# p }
$ F4 M7 `$ B& F7 J复制代码$ S- A( y* a8 y {" q
# n0 m: u/ g/ I. |/ v* J
) M& f$ r7 r( d3 L5 O2 B' A- b6 F! K. k
(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。* b9 I( Y" i1 B/ Y
(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。
, A3 A1 H0 c- v$ }( c3 @/ z(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。
2 S( @4 _/ B1 ~* Q# `: E; A8 k
" ~1 S+ J5 {: p9 s6 e三.如何使用AutoDocker组件
1 g. I* E0 E; G; q, N/ a' T D3 Z6 A+ [+ V1 L
AutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:
5 n0 z$ ]' K# W) G# X从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:
* N. r. n. u* x! }! Kthis.autoDocker1.Initialize(this);
4 G- t7 J- M# `3 |2 M' k复制代码
: u& {8 o1 r5 k+ j" m0 d7 t. y5 v) Y这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~
' S5 X. }" Y# Y, F7 U6 a' P! ^
- k* K1 v* P- v, f1 r2 T4 y% Y. U |
|