|
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。2 b; l+ S2 }) U# S+ F$ C, L
5 h/ m% G4 K, `. D; |- V2 `
5 B% }( L6 W# x+ W: ~/ ?, M
0 H+ v3 e1 \% |/ c3 ~5 i我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。# f" Z3 O# b U1 o0 v# f5 w+ u
那么,靠边隐藏功能到底是怎么实现的了?2 w' k- M8 s5 j
0 K+ G0 g* ^6 _% ]3 A5 o, k一.靠边隐藏的原理: E! g. B+ m% i
' J0 e2 g8 d6 H @5 b$ n靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:0 T/ X, D4 m7 e" O
方案说明如下:! ]! S8 b# w6 c8 R3 F: W
(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。# R: S* E! K: w- P2 b) K, n9 N
(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。. |" j' P6 _' s! e
(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:
8 n, C2 x& a7 c% v a. 当鼠标再度离开窗体区域,则又隐藏窗体。 s% e9 B( k) o' p$ ^) k# g0 q
b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。; Z7 Z* b) w1 Z3 i
* q7 n$ q% w+ C8 Z
二.具体实现过程6 z2 J! ?; G4 X9 K; f) ~* {+ I
: g7 L: H7 {. `& c: P1.基本元素定义
- w) _1 X- `1 L$ W首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:
3 }# E- J! i* n0 e( j0 X/ |/// <summary> 6 h8 D- B: i1 W9 {# W* Q" a& }
/// 靠边隐藏的类型。
( Z( F, {( v8 k) N/// </summary> ) J$ V2 _$ i) V$ E+ V! q
public enum DockHideType
: L6 G- [2 ?- i9 @; ^{
4 z+ Y' L7 ^: F" `& d7 A' U /// <summary> 9 I3 T% ^. G3 N6 M v
/// 不隐藏 + @8 M; p% t. G0 w; F
/// </summary>
' X0 i. P! C$ @" Q. b) ]- X None = 0,
- n* G+ g- c9 h# |) S2 q+ R! K) k* P /// <summary> C7 K5 e/ G- H) J1 N
/// 靠上边沿隐藏
9 ~: e, k8 q3 V /// </summary>
7 C) V; D* ^3 v& G7 l1 D/ L Top,
6 m3 y0 a/ U; j9 G7 s9 r1 t/ Q /// <summary>
5 ^6 U' E9 V' N! a. T- W /// 靠左边沿隐藏 : G8 i* s8 r5 y. I5 p
/// </summary> : L( o8 [& n8 ^* o. p
Left,
" Z/ y1 f0 {; [* o [, c a5 F /// <summary>
1 O% x& d6 I5 F" \. Q1 i /// 靠右边沿隐藏 # r' x" r/ I* B( x$ S
/// </summary> ' H; s- W/ W0 W; `& B+ ^9 `
Right $ m" c/ K5 k# A% P
} O; F7 X' I/ }% L3 v8 V6 Z5 d
复制代码
C& h; O; U% d. T' b! J& D; o3 l, s0 s2 d' o
其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:
; X5 ]0 z6 S" F* A+ w) n9 n/// <summary>
( u, N# y$ ~; e* F! i. d/ k5 G: L /// 窗体的显示或隐藏状态 7 d1 e3 j/ e2 r9 H# O+ P5 u# U
/// </summary> 1 \1 \) r' O; E" r; w/ e
public enum FormDockHideStatus
# q/ ?7 L! }3 l {
7 W, \% z6 V: C8 U /// <summary>
9 t L" r) }. U6 e% n /// 已隐藏
7 e0 {' {; j0 J/ v. z6 c /// </summary> 2 F, O, o9 |, c" e
Hide = 0, ( W. s8 K( X* S0 X9 U4 E
h& j% Z$ @) c% J6 @) C5 H* S( X
/// <summary>
/ v" `6 M ^) d2 ^- C- c v% Q, d /// 准备隐藏
" y6 @8 b- r3 Q! Y+ q8 { /// </summary> : Z6 L6 Q+ |8 |4 H* O ]& B. J
ReadyToHide,
1 I: S7 }! ^' P4 X
. V3 S' l4 W! i$ k /// <summary> 2 w1 P8 P, `8 m% M& v" }$ U! q8 y
/// 正常显示 ! d3 Y$ Z+ ]1 a
/// </summary> ; v2 J- Y( k' r6 z8 M# F9 @( n
ShowNormally " d. f, r9 ]- o/ I. d6 r6 \
}
& E) n) V6 H; l& k# R4 I: n复制代码1 d& Z" k, X+ a# v0 ~3 g# ?
# f1 @' m# h% m$ D* C V
2 ]+ v1 b* N- W n* w4 b) ~6 |2.判断是否达到隐藏条件
3 x1 l, Q+ T/ |) T1 _$ ], o" C' r9 Q! E$ b
很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。& d8 t' n6 Z; x! ?3 i2 D5 K
private void dockedForm_LocationChanged(object sender, EventArgs e) 8 f! l! T2 e: ~
{
/ t [( m1 a2 R! n, x/ l3 t this.ComputeDockHideType(); ; i1 ^% p( Z" ]" [. @
if (!this.IsOrg) |$ r1 K+ l/ K& H ~
{
$ S2 o$ S' K) n$ x% H this.lastBoard = this.dockedForm.Bounds;
- P M7 }: _6 n" H1 ?5 L this.IsOrg = true;
9 D7 u9 J9 h5 a# D/ P+ n" Q }
- N) F( F) L$ u' p$ j, V }
3 L: L: \3 J& x
: S F3 d7 X' O# f2 @/ [, p /// <summary>
% b4 v5 V5 K8 t: A* t E& P /// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。
: t) J1 I' @7 S, A /// </summary>
+ u1 Z( V, Y( @! M2 W private void ComputeDockHideType()
; h( j; d2 F1 m# v9 p3 F. a { . C# c* K7 N! R# u& f5 }$ \. S
if (this.dockedForm.Top <= 0)
( X- h4 B! x& I- p { ; l' h' n5 T: ]. |
this.dockHideType = DockHideType.Top;
+ y0 e! R' {9 ^' q' a6 ? if (this.dockedForm.Bounds.Contains(Cursor.Position)) - r8 {4 p' r5 {0 \$ _! c( u
{
$ {4 o* E# V& b$ _$ ` this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
) Z5 |0 b, Q& J4 V return;
. t7 y5 I( g3 L* V( i0 | }
* X, \' e2 S/ ? this.formDockHideStatus = FormDockHideStatus.Hide;
: ^" Z1 t" _* F* R8 w return;
s8 x$ j& a' ` } F- e1 }4 u0 r& T( c! C
else
/ K/ j! a# I' ~) N; B { # O0 j( h9 a# g- n! ^5 r! M$ B- V0 C
if (this.dockedForm.Left <= 0) 3 f3 t) [# h5 `" y% c
{ / v. G: O6 O) |' z8 ~
this.dockHideType = DockHideType.Left; % m, x/ h* w! y- ~
if (this.dockedForm.Bounds.Contains(Cursor.Position)) 6 q0 b* d* S3 G1 Q; w- F
{ 5 ~5 @9 Z, }4 u+ u
this.formDockHideStatus = FormDockHideStatus.ReadyToHide; `. }' I" n1 |7 Z4 _
return; 1 g7 _/ X1 A* x2 m, `
} Q$ x3 `7 N3 Z1 ^9 K
this.formDockHideStatus = FormDockHideStaus.Hide;
9 K0 `: k$ D( C return;
# C6 d0 K1 K8 |% Z }
! E' Y8 \. d5 W! a% ~ else
- F, F3 t! m S { 3 b& f9 h* D! ^ w
if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width)
$ x5 i6 b6 W5 G5 G% A; {" { { 7 `1 k: \2 g6 n1 u: c- E6 W$ Y
this.dockHideType = DockHideType.None; - L* p! B, Y3 d; D1 W5 s: V. [
this.formDockHideStatus = FormDockHideStatus.ShowNormally; 4 @9 @4 U) z* }! v8 E
return; * J0 [& q5 u1 A3 m, b# m
}
4 C/ ~6 o! ?% P+ b4 } this.dockHideType = DockHideType.Right; , Y! D. K4 x7 s7 q) j' R% {! L0 G
if (this.dockedForm.Bounds.Contains(Cursor.Position)) 9 A) d( K! x+ N- `$ ?
{
4 \; K( h3 s8 Y* z ^8 W this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
5 y; B3 p _- K" ]+ M- _2 ` return;
3 {6 t' d$ g7 N* c } & E/ Z7 W+ E, T- [* _
this.formDockHideStatus = FormDockHideStatus.Hide; 9 Y' f/ z/ q9 ]
return; % j1 e/ C/ q1 U2 U5 a
} 8 ~5 l3 i9 s& d9 J& I, S* ~" K
}
& n' Z' | S9 Z# @* A }/ q* V: u4 m* i: m0 q) o2 a9 g1 r
复制代码3 O6 L6 [- n5 x- U* `
! I/ s8 f. P! z" Y& m
! f! z! b% t( q) l5 W: f5 N
上面的代码主要体现了以下几个要点:
" ^ Y7 o( [' |% r( A/ o" o3 m+ y( G/ H
(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。
6 @: e& p( P8 ]' R; P( v ]! W9 c" d(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。
- P; h; E) r% ^+ p. t8 R(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。
/ W5 w9 L# E) H) o7 e5 x* h. k详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。: ]/ ?5 r) I ?9 t9 n
4 u F2 M: S" r" X3.定时检测满足/退出隐藏条件
1 K" u7 E- @* V2 B2 t) P, e
5 T) F" a' E1 i* ]( o2 s8 u我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。
: Z! @" `6 N. |, ?6 j' E/// <summary>
7 w9 G( N0 n. A# X8 H. [ /// 定时器循环判断。 ' b$ ?2 c, G5 n. r; w2 |- @1 a8 P% j
/// </summary> 8 S$ A' s; u p0 @* a! P. U
private void CheckPosTimer_Tick(object sender, EventArgs e) 3 r" c5 W8 V2 t& _' u
{//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外) & ]) o& a) W; Q- o J1 G o& U5 K
if (this.dockedForm.Bounds.Contains(Cursor.Position))
1 v2 X$ b0 E$ a4 B { if (this.dockHideType!= DockHideType.Top) 7 @& i( Z3 X6 T4 u9 Z1 T
{ # a4 a- f* S6 t. C9 _; h
if (this.dockHideType!= DockHideType.Left) 6 R- I0 T5 D# }
{
7 g, u- V0 C6 d! \/ U/ M if (this.dockHideType!= DockHideType.Right) 7 m- z O- X! _
{
/ g) o& ~4 i: H return;
+ y) d, r( t' G( b }
2 N0 M4 @8 G6 h: y if (this.formDockHideStatus == FormDockHideStatus.Hide)
4 y; T p4 U& b% K {
5 N; a! X Q& S+ l) {4 g1 C this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y);
! e) v$ i" l- T. s+ i1 B5 m return; $ Q* F# Q/ q) P' }) ^, s4 D
}
. P; J# r1 p- N |5 E } ; _* H; d8 ]+ v
else 3 |# T! Q9 m' ^# ]/ V
{
% R7 ^+ y. O3 v, F if (this.formDockHideStatus == FormDockHideStatus.Hide) 6 Z0 Z- s% Z) G$ s
{
# S6 l) O- {) J) o! C this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y); : D9 l/ x% X( }, X+ B' I. r
return; . e2 ~* e/ G8 e/ r) t; R
} 1 g- A0 l6 z1 q) U" P3 f
}
) _" D b9 H5 ]. e5 i. n: C }
+ F; g$ q! E3 _1 S) h8 [ r/ B else
, H. Z8 s/ g' M, Z. @2 E! P! S& K {
?: v$ \0 J9 P( R) j, K if (this.formDockHideStatus == FormDockHideStatus.Hide)
& l, X1 v& C# X4 g { / N& A E* T- {0 V* U$ c
this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0);
1 P, U6 }- M3 a7 m. I return; 1 ]2 l! S S0 u" ^. o# K
}
S* I2 ^: H+ @. n } s4 D# t: v# l3 k) R
} 1 Z3 r" ]# N& l3 g# Q+ t
else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。 3 {" p z4 ]- M# n6 Y7 [ s3 w
{ switch (this.dockHideType) $ V7 w( [) e. e) z
{ & s* u2 [4 D) k" T0 v( Q
case DockHideType.None: , n! J! J, ]0 o1 S9 y5 s) c/ V; m. \
{ 3 G P5 B! z7 `1 ~! n1 F
if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally &&
2 \1 z2 b- a( V v (this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height)) 9 g4 T |, G9 I( G t5 p y! U1 G
{ " p7 m, D) _6 d) h
this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height); - i0 r* y0 t7 J. @1 K8 u
}
" }: e6 Y2 S1 a7 u/ R6 Q' v break;
q4 }. q0 x$ @$ O7 v W, z } , ?9 V. T# q. o* I2 o, `6 y
case DockHideType.Top:
4 Z$ @7 E f% p9 z {
- C3 K4 k8 j* s1 z0 ~1 t3 n this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1); 1 D7 n& Q7 e. t% |
return; 7 `, f( a( R5 a/ n) {) H
} 1 f1 H H8 ?/ f1 q! A2 |
case DockHideType.Left: ! k5 q* T" R+ T8 w7 ~$ ~
{
" \. v5 R1 k8 B0 M" E% e M this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y);
" O w$ L$ @6 e return;
' J# u( |" \6 @: q, a } ! E5 m, l8 X5 u
default:
; h2 a. `# R& H" p { * f4 X6 Q! d# {" ^+ f" k4 ]
if (anchorStyles2 != DockHideType.Right) 4 w" o. S* N' V. \! n( Q; A7 g
{ 8 z; Y( G% x+ e( B: j7 D
return; # z' c" ~; y+ i& B# }
} 4 `7 B Q G( g* f; a% E
this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y);
) X1 _/ S; ^2 i: ^: `2 L return;
1 \$ m% `$ g8 k- J& f: q( \8 v } : e! T% {- e# D3 F; @
} 6 H1 o& C1 s; ]9 T0 F
}
- A# Q) C! W9 N4 O) q. K }
( s- }" A9 Y5 h: S复制代码
: t3 j+ S" G5 M, `' d' r
' @. K& w& H7 w2 x' C1 s3 I% r
* Q0 s( T* H) o9 X9 g% V(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。
& Z8 W8 s/ b# z$ W6 N7 B(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。& H3 Z/ j# y5 T/ @: V/ f
(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。
* I$ E( a3 {/ E& ^) }0 R& {3 k% B6 e( F5 Q5 x
三.如何使用AutoDocker组件( S! u6 b' w$ G/ F- A
/ O |6 z( Q: H$ b# M4 y# b% H7 aAutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:
. ] u4 H" I3 \- l从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:
' b7 \) R9 k$ B$ Y9 B7 Zthis.autoDocker1.Initialize(this);
F5 g. t. i Y- F* R- d7 y+ w& G复制代码
; y* r: U: u3 @: s0 u" ] P% L) Y这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~
2 R: c3 L4 Z, ]2 U! M. j8 y% L4 `
|
|