|
|
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。' D4 ]5 e: ^" |
1 o$ a7 `& l+ w0 A& U* J$ I: X7 R* ]! t6 U" J1 C' }( H
: e5 ~7 P0 s, C- H! \
我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。3 o8 `, N* {9 ]+ V
那么,靠边隐藏功能到底是怎么实现的了?/ o- x4 S" B& s6 I# y$ C
1 Y( K# ?. {- g( ]一.靠边隐藏的原理# W8 o* G: j0 b6 b, Z, g4 G
" i& |. C, F7 H7 o. Y" P
靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:& ^# U# M2 {5 K6 {. \
方案说明如下:2 s% {+ u: V) S# R
(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。, ]# W; l6 E+ e1 g3 r
(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。! b" L" [4 q) I) W6 }6 c1 p
(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:0 W( l o3 |# O, }$ n
a. 当鼠标再度离开窗体区域,则又隐藏窗体。
5 p1 m( [% d% f1 w b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。) `; e3 ]" y, W3 ]" D2 h
6 ^5 `' S! s, l' t3 O! `
二.具体实现过程- y6 q& h' q: P" t1 x0 a, V* W" L* J
* y" @9 c1 \) M, R1.基本元素定义
2 t* Q; r$ f8 T0 {8 y1 ?2 L首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:
" G) E* L2 u3 m2 I6 X' j, t# s/// <summary> 8 @7 S; Q- j# g. }" x/ h; ?9 M
/// 靠边隐藏的类型。
1 @6 u8 [. a+ U5 R$ i5 y/// </summary> 6 u: v* {) Z& y0 g; T
public enum DockHideType
& R* V& c6 S6 h6 `{ {8 m$ [2 M ]" ^7 F: F" H- U
/// <summary> 5 e0 X: Q. ~; P0 V
/// 不隐藏 5 i: S4 K+ `$ a
/// </summary>
: T% P- T$ C/ ~" C- O, X& e None = 0,
0 p" y( ~1 }* g2 V+ e /// <summary>
5 N$ w* A7 _- ]7 E$ g' e /// 靠上边沿隐藏 ]8 r/ [1 y0 }% \2 N% k" u
/// </summary> ( \$ `& U% R+ Y3 m9 K# p( I" A! B
Top,
/ Y: `, z. x. ] /// <summary> 9 B; ?9 V. g( Z
/// 靠左边沿隐藏
/ T! _8 ~1 U; t8 u /// </summary> . y3 s, e) x! i1 g: b3 G7 {2 ]+ L
Left,
8 D9 y- `4 o2 ]0 @! h /// <summary> 8 w% Q, R$ p y! L! R$ `7 D
/// 靠右边沿隐藏
, h3 s+ d& h" D( c% e& \ /// </summary>
8 p2 d/ i6 m4 o: Z( v/ { Right / ^7 I/ u2 c' Y. W% O
}
t* u r# [; {8 d" F3 n! M B复制代码
: O P3 {2 e2 m/ g2 u- u
]) i4 U, W! t5 V其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:
! ~* p# N/ ^) l/// <summary>
6 H/ ?" p- w( g% T8 J /// 窗体的显示或隐藏状态
. c% [3 d w! R. f" W8 v* y /// </summary> * }3 l6 ]; p @
public enum FormDockHideStatus
) H3 B# Y* P1 R { 1 K0 W, _+ V3 B, u, r$ s
/// <summary> ! T+ K* k: z6 h
/// 已隐藏
: j) G M5 F* K, o6 R /// </summary> + U i" l8 P! K" r
Hide = 0,
1 b$ l' P6 f( E+ }" L# s2 M8 f' P
/// <summary> O; K2 a( h* G+ Z$ E# j
/// 准备隐藏 + X7 X0 v' h& g, J* `$ w
/// </summary>
: A; p6 @% H4 [' p, M% A w* k ReadyToHide,
8 l) }8 G0 |9 w; D5 v, w: z1 J I2 c( w- Z; L2 z
/// <summary> . m/ ?& x/ d' t8 ^. |# Z! T4 L& h
/// 正常显示 * J* S/ ^" @6 X7 E+ I8 {/ H
/// </summary> 2 ]: C0 ?+ M1 f; f! m3 b3 L
ShowNormally & F* Z# w# E; ^1 D# ^* m4 ^( l( ^
}
2 a' V( X8 _3 ~+ S8 o* ^& X* r6 h8 @复制代码
0 `0 N" F# R. V' o
8 P, R6 g7 J5 B! {9 v% r8 P3 B' y2 d i: W$ r' P( x. M3 ]
2.判断是否达到隐藏条件3 i7 \: I$ g$ p. V
6 [, U8 L' H. h7 W. B9 H
很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。* {- D& I) J, z( U, d
private void dockedForm_LocationChanged(object sender, EventArgs e) . v+ s( r+ ?2 A9 i, `) N7 Z8 ]/ m
{
: ^0 a9 l( _ f! z8 q this.ComputeDockHideType(); 7 @1 t2 k8 w5 G) u: A- w) T
if (!this.IsOrg) ; }% U: R0 U% } L* i$ Y/ k
{
* {* m( P0 h# j/ f1 E4 s this.lastBoard = this.dockedForm.Bounds; ; l7 c0 F2 h- ^# H
this.IsOrg = true; - P5 r% L1 F! c- w3 w
} * T/ Q1 G2 X! e5 ~( j
} ) W* g# M* ^) R3 I" `, Y
$ @$ i" `) x! I /// <summary>
( C+ j5 [8 r( O% [* Z0 g2 y& x7 @( y /// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。 . ]' x) ?6 L$ ?* i( E
/// </summary>
) ]( x. a4 B: f2 c" N/ J- e2 Y& @ private void ComputeDockHideType() 7 g, x7 e6 N0 X% ^3 W1 S
{
( e0 u5 K# K3 Q if (this.dockedForm.Top <= 0) $ v) _& r4 z1 W3 G4 O5 H. [) P" N$ b
{
, z5 S q9 O$ s6 y% L+ C* ] this.dockHideType = DockHideType.Top; : ^! z9 V& R# H$ w
if (this.dockedForm.Bounds.Contains(Cursor.Position)) 9 b) x: T6 F. Y {) g' L4 F! {: L
{ 8 }5 V! X3 D s) B2 Y. r0 I
this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
$ ~4 `, l6 v# w5 J- M' y. J return; ' l; q' F, t3 ~& B
} 6 b9 S& y# i) p/ h
this.formDockHideStatus = FormDockHideStatus.Hide; ' B) h7 C; e7 E7 a, i B
return; 4 H7 |1 A' X0 z7 D
} - k; O" K1 a3 m" Z9 A* v/ [$ J
else
" E" E* v" a" k+ S {
+ p+ l0 ~+ ?5 h2 d/ P' H5 O if (this.dockedForm.Left <= 0)
, F# b; @: L0 ~$ d# s/ b {
7 s# i7 x( F% v" e this.dockHideType = DockHideType.Left;
, E V) b" ?* z5 c if (this.dockedForm.Bounds.Contains(Cursor.Position)) 5 v1 k* j( i) {! g+ b9 c
{ 0 {; T/ M2 O; \5 q( ~, J+ x
this.formDockHideStatus = FormDockHideStatus.ReadyToHide; . R4 O/ C2 e. L* Y5 h+ H' Z9 Y
return; + ~8 N4 R7 t2 X/ L9 |
}
& o( t# A' p# V/ E" n this.formDockHideStatus = FormDockHideStaus.Hide; 6 g3 O/ j; g% b$ u
return; / M' y0 k( @; x% I
} , r. \: \; U" N8 s' d9 `
else # a O' b, @- n; R+ A
{ % e5 D( h/ a) F9 w# q
if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width) 3 }) v; q$ \2 E: ^9 c( B, F
{ . ~* X" M/ W8 a! a8 z1 j6 V
this.dockHideType = DockHideType.None; 9 b8 C1 `4 J1 g: F7 E* G: t1 K
this.formDockHideStatus = FormDockHideStatus.ShowNormally; & e+ @$ t4 G |1 N7 |
return; 0 F, D7 n$ k0 l& J+ `1 l8 y
} . G, v! L6 k0 S; L# r1 g
this.dockHideType = DockHideType.Right; 2 a' D- V, T* m5 F2 V
if (this.dockedForm.Bounds.Contains(Cursor.Position)) ( v; c: I, n5 Y- R( @7 d7 X
{ 7 ?0 c0 G- _5 g8 O
this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
7 S7 N4 o9 H9 D/ E( j; O/ c return;
n4 k e) B1 j% u/ g$ v } " {$ ]% I7 R' a5 ^; x# k+ H# v
this.formDockHideStatus = FormDockHideStatus.Hide; 8 [ e0 I: } S. i% z7 I# M! G
return; - Q% l, @: S8 E" {
}
8 c2 @ N1 G2 Y% w, [6 G* ^7 e1 T9 c, W } + Q+ w4 _9 E4 D: ~8 g
}
0 @$ ?+ w8 I- z- S j4 m复制代码
: v) t' g$ M$ ^; U4 D2 \
1 i8 }6 r; e' P3 t" m3 H, H) Z! t4 W% j8 d1 }& Y+ d
上面的代码主要体现了以下几个要点:+ T( ~0 x) v3 g
2 F$ X: H0 S& J6 r. M(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。6 s4 g. m3 z& j/ l3 R( z$ W( l" x
(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。
' [( ?) C( ?2 |4 d9 @0 K' z(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。5 T w. e. f1 D$ d( |5 h8 U) E; x
详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。
4 q: u, b n! B! a/ f8 u0 v' g0 u% V3 ] |6 l+ t
3.定时检测满足/退出隐藏条件
: l! d% X# L' C+ q
+ c- q1 s! y* J" h; U. `6 A我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。
4 T$ N2 E0 a/ p4 Z4 v' a/// <summary> - Q2 h; O' X6 p3 z7 n/ |
/// 定时器循环判断。
8 p9 N6 P ~1 T1 k: Z. | /// </summary> ) _! R4 z7 I |5 J
private void CheckPosTimer_Tick(object sender, EventArgs e) 1 C& {( N4 {3 Q; h7 ]2 x
{//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外)
+ r% ^' \' \4 r. J5 z( [$ L if (this.dockedForm.Bounds.Contains(Cursor.Position)) ' d% }3 Q& F" v3 u) B
{ if (this.dockHideType!= DockHideType.Top) 5 i' Y: O+ j) b0 f) F$ N
{ ?: h* a3 [- |- h3 E. H& `: g
if (this.dockHideType!= DockHideType.Left)
: @$ [9 n5 i3 K3 q6 s, j* g' F {
6 t; j5 {: @: Q if (this.dockHideType!= DockHideType.Right) 5 r6 k# B$ x/ ?
{ * E9 n G7 z! k F1 U# R
return; # D, _9 l4 P. G4 t
} . |$ y' V' T; b' ^; C& }
if (this.formDockHideStatus == FormDockHideStatus.Hide)
# O( G) ]4 u1 f) {! Y9 C {
+ i8 Z" {* ?2 l1 m this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y);
. m K) D8 C7 W. W6 Y: A- l0 y return; ( G" n# x& m- n8 w2 f7 N1 A
}
/ P6 t) d# j3 p }
- N! B' N- e% {' Q3 \6 {/ y# |6 p else
, s# e$ S, i4 F6 g' t) \3 E" _8 c { 3 q# O5 c- O C& b" }7 `$ ~' N# ^
if (this.formDockHideStatus == FormDockHideStatus.Hide) u4 _; `( Y2 W1 m
{
5 F2 v! o9 r: @: |5 [& ? this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y);
# E- O, d, R* \/ L, {8 z return; ; p3 v0 z6 k0 d" P3 F/ M4 |
} . y& p9 p7 s% C& `% i; r) y
} / r+ e0 B# p% N9 c0 O
} 9 C* }$ i/ G, w3 b2 x5 m2 q
else ; U* @ H* m* w5 k- j5 z: @/ ~& o; w
{
0 u6 g) Z' ]: o$ R- h4 R/ R if (this.formDockHideStatus == FormDockHideStatus.Hide) 9 I& _3 Q3 s# A. J4 w9 y
{ ' ^! g/ E/ j# v, k& W0 N6 [2 _
this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0);
2 ?3 D7 \7 H; |0 u M0 u return; % E# h8 f7 Y8 w9 ~; e+ ]9 O4 h. ~
}
, p7 h# h" p% l- U }
# u' T( a: [4 g0 C: H8 D }
( q: O9 a) Q- ~; J else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。
+ }/ v* y& n# \ { switch (this.dockHideType)
$ F+ J( }/ M& o1 a) k8 } { " @1 F4 a8 q* `
case DockHideType.None: & `% g W6 a; P- E
{ 3 m& i- ^; c( A( T
if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally && l: C7 {5 [$ i q+ E, t, d
(this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height))
3 ? N0 j' q: ?0 L( Y" j7 a! q4 K {
8 ?. V1 g4 p% o, C: p this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height);
6 b7 o% @; C0 G } 7 G! s: Q$ a# o5 e' ]
break; - C' |" s$ w0 O- J$ s- U' N
} * m0 D, C3 w R" f; K1 z% z
case DockHideType.Top: ) l( X6 b" q& l" d& d% i; p
{
& ]0 R9 M4 m0 O8 @. M this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1); % M- W( d- B; J6 H5 a+ |( ^) j
return;
2 m$ b; e+ F4 p } + _, O- q- A, J
case DockHideType.Left: , t7 G; ^/ V0 p$ \
{ - o) Y: j! S9 ~* |/ G8 O. B7 F" O
this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y); # k7 w! x2 S& I4 Q! r/ ^: H
return; 6 Y# t) k+ D* H! F
} ! {/ |9 J, W# g; c( o9 B L) ^
default:
5 z9 o( [& D1 d; E7 Z { $ U" ?( @# [! Y v: c! R! h% x) d; |
if (anchorStyles2 != DockHideType.Right) 0 Q2 p* X( q# w- |2 M9 f
{
* A$ J5 V9 \( I0 D) ~6 } return;
1 y% l& g. y) I9 z# d2 r# ~ }
& u/ t' v) t- W0 L; Q# P this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y); 2 O) ~ D; A1 Z0 F! Z9 z8 h
return; - B4 ^. }1 v( r$ Y
} ; G! r0 s/ Q4 C' t6 V2 U% O( h$ U
} / ?: |# z. Z# _" Z& |. Z
}
( a; u9 F. x* G: i- Z3 C3 s) A% r* L }1 I7 q) v4 \2 X6 `7 f
复制代码; t b: {+ H5 i9 ]* Q8 D
1 g& }8 h. Q# c% O0 c$ |. f
2 G7 z* V- X% v(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。
, Z5 a, ^. ?: L+ ?# N& T* X( H/ C- p" C(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。
/ R; V/ c6 [+ c4 d& P0 k(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。
# D% a: y ?9 b- F! g0 T
- N: P7 a- c' ]/ C三.如何使用AutoDocker组件8 i! K9 Y# G" ?3 A
3 h/ |7 t9 O$ x6 [
AutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:
4 }' y- }+ B+ R! x: V& r+ j从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:) g5 o' v. R8 F, ^4 D4 Q7 w2 k; I
this.autoDocker1.Initialize(this);8 v/ k3 o6 ]9 R
复制代码, x: ]* b! Z; U4 Y$ k
这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~
; I& a& U; `( Y* \4 m1 K: s7 n7 E- j, e F! B3 J6 u% P
|
|