|
|
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。4 F/ Q% b `0 N& X5 T6 ?) [" r# y
# ]2 d5 f6 M" J! n: w3 {; s' ?1 a+ |1 s1 }
) M- h3 f) V W3 j8 F3 }. H% Y
我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。
, H3 F1 h% l: L. B那么,靠边隐藏功能到底是怎么实现的了?
, x3 S; R2 s b4 i2 {: c/ W& q
2 ?, d4 }: b0 ]0 k一.靠边隐藏的原理
) F! K1 i1 w) c9 `( C, i& p- x$ F4 Z+ P' \( {* w
靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:$ O+ l7 P& E/ M( @9 n2 W1 x& n/ R
方案说明如下:
1 k! o/ O7 s$ n$ }5 d(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。
, o! b1 G- V" X2 K; J9 N5 L(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。5 L3 i k- o0 ]! Q5 t( W
(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:
/ O; l8 A2 R$ ]8 q; C a. 当鼠标再度离开窗体区域,则又隐藏窗体。, x$ w" T+ c9 P4 r) i
b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。
; J0 i) I! ~% m3 X5 J% t: W: G- r- ], y3 Z& g
二.具体实现过程
$ y9 n4 r0 l# C8 y- Q4 @& T6 }% t2 R6 x1 r: b1 C. u# m: m' W
1.基本元素定义
! w* o' [: K7 |8 R4 j8 h首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:/ }" P: U+ G5 [4 k
/// <summary>
0 V! k* l5 B* S! @5 B$ }$ n% I$ t4 `/// 靠边隐藏的类型。 9 T; e) p5 h3 V& C+ Q
/// </summary> % E8 J$ V! N, x" D) A- S3 S
public enum DockHideType
0 J0 n# R& }/ O z" Q% p{
$ l9 K- p, m( n3 k2 v/ q$ y /// <summary>
: ?6 c* N6 T! C5 U /// 不隐藏 1 @, u' E5 }; v7 a; A' g. B- E
/// </summary> / _, U" u. {- G! H) s2 \
None = 0,
p# R# g! u% Y6 X: A /// <summary> 6 o! s7 k! Y/ G8 t
/// 靠上边沿隐藏 G9 r, j2 V3 |+ T4 O8 ]) I' \
/// </summary>
* O, n4 w" T3 h8 n t) @3 P8 d& b( a Top,
* F/ M ?. c& w5 _% X7 v /// <summary> # Z! y/ c$ ^1 f: R, P- J
/// 靠左边沿隐藏 ; j- [& x4 L! z2 z- o
/// </summary> $ ]9 G( _3 g4 ~% G3 D
Left,
' ]/ m9 ]9 H6 y! n. W" J /// <summary>
F& {1 F5 y7 b, P3 z/ i /// 靠右边沿隐藏
: e9 h' w; Q# h; A0 _; l9 S /// </summary>
/ w1 v. B L+ l Right
# D7 K5 i1 j, t9 _ Y+ P3 m}
. s* S+ }4 k4 Q9 e: p. u复制代码
$ j1 ^9 i8 w8 S5 A" a
/ V& S6 c; y% P0 J6 F" x其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:, p/ ~" l9 B4 `8 ?- s7 q
/// <summary> 2 A1 D$ m) P1 M% A5 f
/// 窗体的显示或隐藏状态
6 ]. D; T1 T8 u3 v3 X/ ~4 [ /// </summary>
. a# ?. z l+ {& U: f2 h public enum FormDockHideStatus
. k1 I; B- B/ t" c: {- b/ P {
2 ~: }7 g) T! {& ? /// <summary>
( y; |; {! x( y6 X; w' ]8 C /// 已隐藏 # S1 F# ^7 i# l; {) v
/// </summary>
( S5 V6 W4 B0 J2 }% G* [3 A" x Hide = 0,
; u9 v$ z* L! A- v: w
; h9 T8 R4 Z# ?5 t* c /// <summary>
" S2 e# a5 m+ q /// 准备隐藏 5 u2 e; A8 n+ E6 }4 _
/// </summary> 3 u% m2 A; H3 E0 J# {: {
ReadyToHide, # j1 K- u# [8 \( I8 \" M
$ f& V9 g5 \* `( W. n& Q* z2 Q /// <summary> 7 J6 H3 Y3 c: f3 h4 G
/// 正常显示
2 h0 h) y6 p! G7 P) \ /// </summary> ; Z; [0 m9 s& d1 u9 _! o, F* A* q
ShowNormally
6 i0 K; f6 H1 l- R6 } }
5 U, ]2 ?7 ]# a% M- w* h复制代码
& z9 q/ q H5 D- ~5 b4 Q: m k5 q2 t/ J0 n6 A' D4 C8 f
) G3 _3 B% g8 ^5 M# ~" B
2.判断是否达到隐藏条件
/ t& D% U$ K t! W7 q! Y4 x9 g! }$ D# \) K R
很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。
/ \$ \8 K& i T. H" R5 bprivate void dockedForm_LocationChanged(object sender, EventArgs e)
9 D& k8 h) F6 R6 v" r( T {
/ n5 [4 G2 ~' f/ i6 ?% x this.ComputeDockHideType();
" a1 _+ w% A6 Y$ }6 C. Y if (!this.IsOrg)
% Z/ X, P M: t0 x) m {
+ Z, w6 n$ q1 ?2 M. h' W% j this.lastBoard = this.dockedForm.Bounds; & W' `& x1 j! U/ x* ~. O: ? ?
this.IsOrg = true;
( t3 j7 u" h' M# ~2 a } ( p4 [" D5 f& b# }5 H
}
7 j+ W h9 m8 v' _3 n) j9 D% i4 e. z7 r
/// <summary>
1 Z9 ~8 S0 W! c1 @) G /// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。 ' a6 J* [$ R5 Y: N( P
/// </summary>
9 @. k% I* _4 `& K6 I private void ComputeDockHideType() ( ] ]. E/ t) L& n3 V# t+ n7 t
{ 1 C/ B+ K: W5 v* F! g/ f
if (this.dockedForm.Top <= 0)
- O) Y: @, H( [* x& l5 ` { , m* q7 c9 U8 I- u! X
this.dockHideType = DockHideType.Top; 2 | s t" [- ~) X3 I
if (this.dockedForm.Bounds.Contains(Cursor.Position))
* u4 H# b! D3 ^2 T$ |+ D' V% [ { 5 y% o, S/ ^7 K2 r' C! s
this.formDockHideStatus = FormDockHideStatus.ReadyToHide; ) D& ]+ _6 u8 v* X: S: n+ u" H
return;
- J2 L9 i: }6 d, K }
; D9 N2 F1 J0 @5 K- W this.formDockHideStatus = FormDockHideStatus.Hide; ?: V' F% t- S. q! I! F5 v+ U
return;
" M# r; w( _, c" J2 y% u4 X }
# j/ ^6 e3 S" y else 6 B1 @3 H' p' s: }1 S; L: @
{
3 o5 O& U. p3 M8 T0 ^ if (this.dockedForm.Left <= 0)
& W+ K+ M% X9 i { 4 P( e; c9 ]* ?3 H+ M# I" k
this.dockHideType = DockHideType.Left; / V" V4 ~1 A* b
if (this.dockedForm.Bounds.Contains(Cursor.Position)) 9 V( z, M- O8 \9 \; Q+ \7 C" ]8 `
{ 9 n# m6 U, {3 f. {. j% W
this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
9 g# r+ i A; U b return;
! G$ I# f! M8 l9 a8 b } ) o+ S- p1 {6 U+ z
this.formDockHideStatus = FormDockHideStaus.Hide; ! H& ^1 N$ f2 `3 n
return; $ ~7 V. J, o" m! C* S7 i6 A
} 8 w o( H! P! |3 ^& Z, ?
else
# s) q+ Y, |% }) ~+ A0 j8 m { 8 w; }5 x: c5 u3 R$ G6 e
if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width) + K F$ ~" a0 d+ L$ Q( M
{ ) ?+ R5 g( ?; c' `
this.dockHideType = DockHideType.None;
2 @1 J5 ~: k; W this.formDockHideStatus = FormDockHideStatus.ShowNormally;
8 V* O8 [6 `7 Z L% _* p return; 6 z& l, y/ `) M& h
} % ?7 d/ z% y2 o, D
this.dockHideType = DockHideType.Right;
8 f) U4 V* t* T" J! K2 a, f: B if (this.dockedForm.Bounds.Contains(Cursor.Position))
8 B& ~+ k" ?2 X6 H. t$ F { - C. b* t! f" V
this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
8 H0 v$ Y& s0 D return; 4 X# w- Z; ]8 A+ R9 a9 X
} / r8 \3 p: t# P( t1 i/ }' k
this.formDockHideStatus = FormDockHideStatus.Hide; / ^/ Z+ j! o# m9 b
return;
8 C) ]( H: G3 R$ J. h } : Z8 P1 y3 `6 J, }$ D z
} 1 j! d* t: F1 ]
} a9 l+ e* M/ k, S& I$ A" B
复制代码9 Y4 i% a: O1 ^. [
# j( ~; x2 Q+ w4 g
, r: v! j+ T4 Q7 O' x$ U9 b上面的代码主要体现了以下几个要点:
7 R* Q, e8 l5 T; D" H
$ X. v+ Q9 e; N5 C5 a- {9 b6 a: _(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。! m+ L8 H, A) E( X
(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。. Q3 q% g2 r/ a& T+ n
(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。* x7 W4 i7 j9 `/ F3 D. d
详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。$ r, x0 h2 M2 e: E% G
$ k* @7 B: w' @
3.定时检测满足/退出隐藏条件
3 P3 F; Q" S+ [9 x( B( }
2 O+ z! d3 p$ y我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。
- W' e6 y- `; I t, L6 r$ R" X/// <summary>
* p! K S, d/ G$ n+ P( V /// 定时器循环判断。
3 {3 G1 f' v5 r: M5 L* F /// </summary>
3 g$ q" s% K) m private void CheckPosTimer_Tick(object sender, EventArgs e) . c8 z1 U. q+ L' [% e; |
{//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外) ! W' {, b$ V5 O
if (this.dockedForm.Bounds.Contains(Cursor.Position)) ( _! e: @4 x5 g5 c6 J
{ if (this.dockHideType!= DockHideType.Top)
/ N( }7 [% q9 X# R9 w { # y4 ?' o0 ~) `4 Y: C( Y
if (this.dockHideType!= DockHideType.Left) 9 A* b+ z4 M6 F# ^: S% Z) g
{
+ n! ?+ z% S! J if (this.dockHideType!= DockHideType.Right) . k! R& j- P- D# i( e& _, p8 D
{ - z" l) L. p2 M* U( C
return; 3 X# ?& j4 E3 Z: V* N/ r, v8 L
}
. o: e, }( E8 }# n if (this.formDockHideStatus == FormDockHideStatus.Hide) 1 l6 N B& s. n' H
{ 4 Y6 E" p0 ]" T2 ~/ v( w: m0 P: s
this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y); # R& U2 _! J2 S
return;
2 H$ c6 K' Y' }! P u8 \ } / g5 }+ G- @& f5 o- j* A( k
}
, M" m4 d$ o/ I, z& D) ^ else 1 { q1 p. v1 r& ? `( e) O3 T
{ 4 F" i2 t' }0 h1 E: [2 f
if (this.formDockHideStatus == FormDockHideStatus.Hide)
: Z: \3 d1 Z4 E) z4 R5 t {
' B; Z, a/ Z, T8 e. K this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y); ; e% c) m2 c: T( ^, U# p& @: ]& ]5 v
return;
% f0 H+ F+ w8 f: X' G }
/ f" \$ g+ X& g; l3 r; X! X } 8 T: _) p* U- S
} - N. b! X1 g( ?
else " [, f/ A) i8 J6 ?
{
4 I/ T8 g/ T, v& z6 P# T if (this.formDockHideStatus == FormDockHideStatus.Hide)
/ S N( Q: A* X/ _3 p1 Z* G: X: m { / f) ~; M1 ], ?; L
this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0);
6 N B* U3 J. j: f `" ] return; + A( W2 g! Z4 j- p! l" J. `4 b
}
0 H) L) u% R0 p2 [7 Z } + M6 b2 y9 I7 y3 x4 w D% A
}
; h. l1 R* C9 W5 q5 z8 {6 O else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。 & m6 K* k7 \7 X1 L
{ switch (this.dockHideType) ) F t5 }" q7 P8 I6 l* ]
{
2 j) Z9 T3 Y; S6 i case DockHideType.None:
) K2 D! N; \" B {
; P2 v# Y, J/ ~( S if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally &&
, v- m, q) b7 u/ \( | (this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height))
7 u8 P7 {2 r0 o* }+ e, c {
6 U& s; s B# q; E. W# A this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height); % e# D' l7 j: ~" n; R
}
8 U- p4 E6 ?! A, ?/ V+ L break; 6 M7 L. r4 y0 C! E9 }, S( P
}
, }. Q& ^- T3 k; U case DockHideType.Top:
! j5 o1 M5 _1 F' T1 t8 x {
' u5 X# G" e$ x4 [ this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1);
3 X J* b5 ^9 q' j- } return; 3 U' ?# W, |! z% w) P2 {1 \' f
} % ^9 d* T/ i z6 J
case DockHideType.Left:
, K: ?% Y5 O+ f O9 a! B9 x5 _: r { ( T; K( `) K% D9 B
this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y);
% N, q, b6 P" n8 L R S return;
; }# J' m5 Q- c( \# [9 _ }
7 z' p n. E7 S2 Y default:
) n7 D0 f5 u. F7 C: J { ) l5 ] c4 U' E# m6 r1 C
if (anchorStyles2 != DockHideType.Right)
# j b% f* i& C# O P) H9 V) ?+ E { 8 K e6 b9 |; B. V/ T% \
return;
, S4 A+ P% k/ s! f1 n } ' O/ \; W4 X0 b+ H0 c/ V' U7 `
this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y); - `" I$ f3 A7 h9 ]. }, f% H
return;
3 M Q8 p( d) k& ] } / [! Z- P& \1 T8 e9 ^. y* M! E: u
} 5 H1 z4 H7 e5 W4 K+ F0 q7 d
} % J# o) Q; k! y, ]
}
! ?8 ?; \: W& e+ t复制代码# J3 j. S* \( Y- k2 J# i
7 v- {4 y: O$ O0 r$ k* o
6 d, p. w+ R* \8 V5 t% I' G
(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。
3 m: ~$ x! `& g( d0 O, x(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。
+ f; R4 h9 Q+ r3 d7 ~& I(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。0 _( ]1 H- W, S3 b2 D3 r g7 S
# g, Q. _& N' O. s/ }' m
三.如何使用AutoDocker组件# v7 T( w- k7 x/ [* D4 K
; F+ d: Q2 I7 L- _# x7 R5 c1 hAutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:
, V/ H H& Y3 h" W/ i- n& Y从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:
6 y. D) ~. A# h0 ?1 }3 z9 B. Wthis.autoDocker1.Initialize(this);: e" [" t! u+ D9 I
复制代码. |' q0 w: C& D
这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~5 W5 P; J3 s5 o3 @
& L0 `7 P; o; N. R$ \8 R6 b) n' [
|
|