|
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。
9 l- C% o8 E% i/ z, E4 \5 @0 J8 R. O1 j
2 P+ g% ?& ~( m8 x$ q
1 F' w1 B7 p5 H) M
我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。2 o2 |* g# m4 F3 I4 t( T
那么,靠边隐藏功能到底是怎么实现的了?$ ^' Q6 v0 L! K* e1 a7 l, y
8 r) i9 c# Z# W2 G; s
一.靠边隐藏的原理
9 J7 s) \: M; D. M- @+ Z1 T3 d) |1 i
3 M U$ Y. m$ W9 w靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:
& w0 ]" _$ z5 m q方案说明如下:4 y4 v: U1 Q0 L3 }. T' Q% j
(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。+ W: o9 [& _& F1 R/ r
(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。
9 ]3 P m1 ]9 W(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:
. n- J$ K# n! U a. 当鼠标再度离开窗体区域,则又隐藏窗体。' f1 b! `" n1 \: M# `0 _
b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。
7 G5 q3 `! ?! y8 k1 h @3 e% \' G
+ L% z7 ?) r) F& M二.具体实现过程
! |1 `$ q" i3 d$ L# P7 R7 b
. b4 n/ ?6 I2 t( t5 C. Z" a9 U" N/ t1.基本元素定义
% h- m( P3 I' ?3 P1 X+ k2 c5 F首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:! \6 m3 j7 b, ^ I2 k
/// <summary> 4 j0 b3 n2 G# L" K: j
/// 靠边隐藏的类型。 9 \' C: s3 B/ x/ d, r# X6 E: Z
/// </summary> . Y9 ^4 p _# B" P4 m
public enum DockHideType
/ j2 ^% l0 X/ p& W2 m6 I/ k3 P9 ^0 ~1 w' K{
5 k/ ^ H0 \/ n" \8 ]' b( _' j6 P /// <summary> 0 T/ i$ Z4 t% m
/// 不隐藏 5 k5 d+ Z6 [. T4 f c, c. l: o; t
/// </summary> ) d8 \: s4 {( O% N9 Z" L8 I! s
None = 0, 3 a- Q7 P" N( N
/// <summary>
; h4 {7 e8 o, X1 A: n' E /// 靠上边沿隐藏
: v+ J- ~7 \' e2 z0 l9 i: m* u' g /// </summary> $ V% Y/ t) c4 O5 `! V! d
Top, $ `( T. h1 P; I
/// <summary>
" b$ e+ V* d) j$ I: z9 p$ g$ O /// 靠左边沿隐藏
, ]$ T4 `8 L; A8 X" Q) O. b /// </summary> 5 `8 D* ~2 {7 T
Left,
# P2 X' ~; B- ^: |2 x /// <summary>
, c: R, n+ a6 V q( y% M! x( x /// 靠右边沿隐藏
! R0 c% C3 B O: A' ?4 d! u/ Y& e /// </summary> ' N0 ~, k. b9 _5 _: P6 M$ h
Right $ v& q" A1 _' ?6 d4 u1 v; u- q
}
3 \7 |; X; t! l! m2 `复制代码. J) J7 B& e( ?5 F) {) O4 `: r
/ }4 s# X- C( q; Y' w8 N$ R其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:
5 c8 s: Y6 y5 P# a+ f# j0 X# h4 }/// <summary>
1 m& q, X# V! ~# | /// 窗体的显示或隐藏状态
# L/ [8 B; j! X! U2 o0 p; a /// </summary>
; Y4 ?$ b- D3 t9 Y- k3 @ public enum FormDockHideStatus 1 O$ P: n- h5 v" Y& l$ Q. _7 R
{
6 @3 Z2 j1 y* ^$ {- n /// <summary>
) k. v. L& i$ v* c+ L1 h /// 已隐藏 6 ~: }2 ?$ O- p
/// </summary>
/ U! A; K/ j; T! o: g Hide = 0,
, h( ]. l$ I8 x$ t$ N! h [$ v5 [. N
/// <summary>
9 g- l8 S+ ?( c( [ /// 准备隐藏
+ w' Z; w- d" Y1 u5 b! { /// </summary>
. d. p+ @& @/ z) Q M ReadyToHide, 9 p: t, q/ k+ D3 S: g
r8 l0 h! B/ p7 \1 l& J" f* B. c /// <summary> 5 C) D9 @ I- `: k) z! U! T
/// 正常显示
6 @4 F- R: v* z* ` /// </summary>
4 C/ Q+ u7 ]+ O9 X/ v3 o ShowNormally % `0 L5 q! T+ ]* _9 t( a" k
}
% G. Z, j# V% ~4 h' y复制代码% X6 |* z" k) I9 ~7 ]' T, l
6 c, o% ?# m" K8 o. n; q
% H! \6 ?# D( y0 e2.判断是否达到隐藏条件5 B$ I) |( l) ?
9 Q5 n3 `* ]# F8 m
很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。% c1 Z) c: E, y8 h7 e+ R/ h
private void dockedForm_LocationChanged(object sender, EventArgs e) & o( n( l3 e" Q" M# a7 G2 |
{
( W1 d% D3 R+ ?( f this.ComputeDockHideType(); $ }6 ~/ X1 d8 q8 u+ e( o* V/ @
if (!this.IsOrg)
1 E7 G% U( {$ s { * [+ \8 D% @5 \2 T1 j! s8 \
this.lastBoard = this.dockedForm.Bounds;
- m$ c6 j Z u this.IsOrg = true; + I6 Y! d9 T' x8 ~1 i0 h+ u( ~
} ( Z8 h1 m9 @$ ~& Z5 P
}
( L& h$ R+ d/ R' [
7 [) L9 A' n1 ^9 H9 H& J /// <summary> 1 W; c' T8 c( j0 d
/// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。
* L0 x, S. O% I! D* ~6 Z /// </summary> # R) [" ?# j. {0 l# f# X4 j
private void ComputeDockHideType()
7 J7 Y( F. l7 @: \ Q { 1 O6 `" g4 `5 r2 @
if (this.dockedForm.Top <= 0)
* }/ R. f- L8 B5 ^5 S4 E { & d9 `+ M3 V7 i1 O
this.dockHideType = DockHideType.Top; " \: S, r5 Y9 u, ^) s! {; `
if (this.dockedForm.Bounds.Contains(Cursor.Position)) ' L& z; I0 t, Q. @ I
{ - p: i7 x( R+ `4 [* O
this.formDockHideStatus = FormDockHideStatus.ReadyToHide; 0 Z/ T) S. d4 N$ |
return; 3 r1 P' c( Q2 C! @& B6 f
} 3 p! r V% }9 K! {6 |
this.formDockHideStatus = FormDockHideStatus.Hide;
- K/ Z% q1 V$ u" R4 y( Q return;
" P4 ?! ~# ^( V } 8 A/ P% B. a# D$ Z* ^+ [' X
else 4 L, L" ]0 ?9 V: C$ T
{ - K {. K# x9 V8 s3 _
if (this.dockedForm.Left <= 0) G9 t7 D' P8 h
{ % i9 ]" f/ P2 C( n9 h
this.dockHideType = DockHideType.Left; - y/ Y0 k# x. n7 ?: B7 U* L( Y g
if (this.dockedForm.Bounds.Contains(Cursor.Position))
# W6 T. } e8 T/ U {
+ h+ b9 B/ g& A4 g this.formDockHideStatus = FormDockHideStatus.ReadyToHide; & C. _2 n c4 ]' u/ Q: R# R% S
return;
0 e( X; V" m2 b8 J1 \; [3 g }
: L- S& G+ p7 s# K3 R" r( F this.formDockHideStatus = FormDockHideStaus.Hide; 9 W! m# e0 H7 w; h( n% W0 Q2 W
return; 2 a, E, C m* q. n$ g ^
}
, f$ _) ]! B# P7 R else & A0 U& Z' F$ `( x* _3 ~9 Q
{
. ?' W' X' ^$ I if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width)
# @+ ~/ H8 t& j5 t$ x3 X% b( L { # _/ S5 j/ {! z& o# c- g
this.dockHideType = DockHideType.None;
1 m8 I2 _3 X9 I+ r3 _ this.formDockHideStatus = FormDockHideStatus.ShowNormally; 3 d% @4 j I% w( O/ }
return;
( [4 r% l8 V# A/ u+ N# n2 w% @/ X }
R2 J" e' N% x- ~: {' M this.dockHideType = DockHideType.Right; 5 j' ^' n8 p' I5 z6 N6 J
if (this.dockedForm.Bounds.Contains(Cursor.Position)) & T8 g, |! R3 H6 Y
{
& v0 E) m& P" P2 Q$ \ this.formDockHideStatus = FormDockHideStatus.ReadyToHide; 6 [& m5 }; j+ H1 p' z
return; 9 O- c/ {4 F& t8 t- y; J1 f! a
}
6 S5 u" Z1 h" B5 H this.formDockHideStatus = FormDockHideStatus.Hide;
- J0 H e3 E% h return; * A* }. n( }9 D) i
} $ J0 M- d; L2 p- }" p8 k" H
} 6 J9 t' ^* A. a5 Q% m
}
; p5 T1 G: V1 [% q/ D6 f% }复制代码, N! t5 K8 s5 G T. v+ _1 x0 q
4 N7 G, O5 F) g. z
- O$ M; K$ k9 v上面的代码主要体现了以下几个要点:, a, x- O) B2 ^% U3 V
H+ ?( ^6 W6 Z/ o5 r4 F+ B
(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。
" X* I, y- C7 N1 n8 w. G(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。
7 L1 X. C, i( Y(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。
: u. O9 c; J ?# H详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。
* E$ h0 A+ ` c9 h4 O2 U8 c/ I7 Y/ B- f# U+ g2 G5 @
3.定时检测满足/退出隐藏条件- B. o% B* ~/ k; J% [ ^; h; Q
$ s& z& p. T8 T- S8 U
我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。9 h6 y2 h& B+ w. p0 c% s% Y8 W
/// <summary>
1 P. L- i4 \7 @6 }) e, k, Z /// 定时器循环判断。 0 f1 R2 I# T& |# Y& R- ]
/// </summary>
$ a+ Q1 a# v E9 q: Q7 _/ H) g private void CheckPosTimer_Tick(object sender, EventArgs e) ( w1 Q. c5 n g
{//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外) + c5 d' K! ~# V- b6 s% M
if (this.dockedForm.Bounds.Contains(Cursor.Position)) 4 V- A* P1 D, v) x5 g4 k
{ if (this.dockHideType!= DockHideType.Top)
+ V, o0 P6 A' N& ~! W { ; j6 s; D, X9 e) k3 F
if (this.dockHideType!= DockHideType.Left)
, |' F* E+ h# y6 U. U8 u7 D% \ {
! o" \: F* l0 o& Q4 o! a6 K if (this.dockHideType!= DockHideType.Right) ( Y5 | F/ Y: }- N5 d$ k3 r0 M
{
6 I6 @9 L2 I* |3 g return;
$ _6 R: K4 D+ m' h5 J9 d. P }
, m+ s# G; @! F if (this.formDockHideStatus == FormDockHideStatus.Hide) # {5 u. A6 q3 j
{
4 J) Z6 L8 c) s# ~- D: x this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y); " q) x- ^; i/ z! p
return; 4 B2 y: A2 Q7 |, D7 }+ H
}
8 x# j8 P8 [% u7 v7 [* \0 w } " }, W) s+ W/ M+ r9 ^" Y
else
0 r4 H& D( d g3 U, j {
$ n( s4 Q1 e: _4 V$ U if (this.formDockHideStatus == FormDockHideStatus.Hide) - z/ V$ ~* j" n8 K# k
{ I1 ~2 J! J+ {2 R7 @9 d
this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y); ; L0 j- C& R$ O4 K' H9 ]5 s
return; d" f9 j$ Q0 |* P( M
}
2 L9 f3 p8 j' f1 j/ o- J2 h: ~ }
& n7 C/ G& t) n! s7 `+ p- @; J }
% D, s) z# _; d. t7 B9 I else
/ c# I6 p6 Z2 B: M6 D4 S/ [ { # x3 w2 L% f( h& P) {
if (this.formDockHideStatus == FormDockHideStatus.Hide) - I- U8 L' n$ {: V- ]; L
{
- @% V) W. u9 b m' `3 j( g- U1 {) ] this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0); 8 s2 z. M6 | b/ N+ o% u5 @
return; 1 V n, ^7 @2 n- ^
}
+ L9 @# t" u" }: M } $ p/ i* U7 U+ R) O$ q, h
} 4 j0 e" y, A3 B' ^# j Q
else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。 . C U5 R4 n; `7 E
{ switch (this.dockHideType) 5 K: e$ N) R2 [+ l
{
* S1 R) {! W' y8 U# s case DockHideType.None: + `. }* s! z% [( M2 m' c1 ]: K
{
) ?& M: D7 |9 N1 E1 M if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally &&
$ h3 v( i) [0 O/ s' d6 v (this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height)) 4 W5 [# M9 s) l' ?( X, t
{ ; Q/ \/ N6 S$ U8 B4 x# {
this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height); # v$ t5 X v. L- Z4 f& E
}
5 [& |4 B' j" r O( i break; ) C( o2 ?5 V3 J. h9 _% U- o* E
} ! p$ W; E: P/ \: G6 r: ~$ W( {
case DockHideType.Top:
0 B- Y9 n# y2 X4 L1 X+ b2 n { ! Y3 ` f- ~! O: b0 k
this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1); 8 p5 L3 s3 l& }! i8 p v3 j
return;
% v4 z: {: u' c( |* X( ^; K }
* E6 t3 t& f7 _ case DockHideType.Left:
: O! r0 j, A3 K) K$ P {
# @9 D6 u/ l% h' Z) `- k+ d% y$ F this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y);
, g5 M6 Y* k& ] return; 5 F7 K( u; u# [
} - q7 V* H) p) w- t6 S4 M& j" p
default:
$ f$ S5 g7 ~3 G: [ {
6 u: S$ j N7 n5 x: x% i# I if (anchorStyles2 != DockHideType.Right)
: r- F/ J! ?* h2 @ { ; i" ]! }1 c5 Z! u& Q1 Q& ^. _- g
return;
2 ~/ s& n6 t9 v4 c' m }
4 d/ ^, j8 F' ] this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y); 2 z, J: u; E7 N1 ~
return; * S$ v3 n) C- ?1 N# s/ V
}
" `, n0 I0 f# ?: j# J }
/ ]$ n. n7 m7 u1 R C# x. p8 Z) G }
8 H _/ z4 p+ M+ N }2 X/ P9 H/ Y4 t) B" N
复制代码# e3 m4 z% i! c+ S
: A+ o: o& x- i' b2 N6 ~
R, L9 d1 K. j# r4 M
(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。; U8 Q5 }2 B! s) }8 ?
(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。7 K: H* v3 D) z5 y- C' W
(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。
) v# b( @" g. q; j2 e4 w# n# `0 N
三.如何使用AutoDocker组件
2 V* J) X" a2 w! C! r: M2 w1 E8 h5 m5 ]
AutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:9 h5 Y0 K9 ?0 E4 k! J% ^. u
从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:
2 O# M! ^, c2 g* d9 H' @6 Z' H Kthis.autoDocker1.Initialize(this);9 K* Z0 D' Q( x. Y2 ]9 |
复制代码
6 u( _+ F" y+ V$ k) M这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~
! Y# M% v, D7 X9 ?
' @ g% v& P) L |
|