|
|
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。
& n( f. c, \7 e" w( k7 L* |0 W3 `6 ?- `% [1 y
6 k4 e2 ~% n* I1 M- I
1 c1 _9 g; ^- t我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。' b4 B9 p/ f. U$ E) P
那么,靠边隐藏功能到底是怎么实现的了? e' r. j0 F+ l" K" O
& k' `9 O3 R1 t1 U5 Q一.靠边隐藏的原理
0 T% ~, G$ {5 V$ H3 U
6 }: P+ x$ f* Y+ Z5 S靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:/ f; D' y7 K, G
方案说明如下:
& M7 a& i" M* L# Y; \2 Q8 h(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。; V- @6 j M4 y9 P
(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。
6 c8 S; ?: {3 [% t(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:
p: \% [( e' B, `* H' P7 Z" C- a2 O a. 当鼠标再度离开窗体区域,则又隐藏窗体。
4 B* L2 K8 K/ {4 x; }) ] b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。; w( ?' z L5 ^- C2 z9 a6 X# o: ^
+ d% X2 u* Y* e* w
二.具体实现过程
( V/ S. m" @" u9 W3 P5 Y, G0 }/ ?) k8 u- h
1.基本元素定义; U. ?+ f y! R. i- k) h
首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:7 R$ w- A; f- o3 N+ B( M! p' |, u
/// <summary> + ]/ {5 A. q0 ]7 k& t
/// 靠边隐藏的类型。 5 |0 p. s0 Z9 o' j6 Z7 C3 n2 `! J9 P
/// </summary>
& A+ @! y: j# I( Q9 b/ b( \% a* kpublic enum DockHideType # D3 G \+ B e: ^2 C
{ " v4 Z6 k; T- O/ A+ `
/// <summary>
& M4 c( P. @, ]8 d9 l; c" a. C* ~ /// 不隐藏 & E- y4 F$ X$ v2 Q" i7 s( r
/// </summary>
' S0 [ M8 l2 l None = 0,
w$ g& e& ~3 T' m. ?& O; ~+ X /// <summary> . k% X: ~1 I/ S1 R: [- q
/// 靠上边沿隐藏
$ S5 E2 Y$ j0 @3 ] /// </summary> 7 t2 \% W; t% @
Top, + N1 r, R! {" `& b
/// <summary> , \' k2 z& c. E% _$ ?
/// 靠左边沿隐藏 ( u$ a/ P, {: f; s
/// </summary> 7 Q) P6 w x9 T: l N
Left,
) O% [ B! o6 v% E- ]# w2 J /// <summary>
0 A; a; d: `% j /// 靠右边沿隐藏
( @5 ^; T: ?: @8 q /// </summary> $ |+ \; }% r, [0 Z
Right * n+ L3 B+ H u8 [
}2 l' X# N7 u5 q- s, ^
复制代码* U+ G5 ]0 J) O: i' g
w! j2 N2 Z5 _% Y其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:
" Y; B" n: H6 c5 c/ I/// <summary> ( U7 k4 F! |0 c6 a/ i/ B
/// 窗体的显示或隐藏状态
0 p& @( ]6 F6 ^- \ /// </summary>
; X: [( i/ S8 c' t& j public enum FormDockHideStatus 7 r; N9 V+ N# t6 p7 |& v
{
5 u0 G+ _! f- [ o /// <summary>
2 \7 K" M# ?% P9 ?8 P% \ /// 已隐藏 0 | y2 _5 N. [0 F) p1 \/ |4 i$ n; Z
/// </summary> # m) p. L' C: w4 I" A
Hide = 0,
+ k4 @7 l9 b& H* V9 o) E9 g. {& I$ g8 `) ^4 {( z. F" Q
/// <summary> $ f& u3 R3 U* d. m
/// 准备隐藏
" z" ~6 u) X* u* |; [6 {% P: z3 i /// </summary> 3 V4 H; T; S) X8 n( B, Y
ReadyToHide, ) K5 h& P+ x/ t1 |( ?
# V$ F5 D0 W# m2 `( _ /// <summary>
! v3 N1 Z! [8 z. F( Y1 v2 r8 | /// 正常显示
: p% N7 J$ B7 T0 N /// </summary>
2 h( o4 B9 Z, t ShowNormally 5 Z: o; @* c# a0 L! W
}
2 E' S2 t, k$ v* s% _. _复制代码
3 j$ @/ J( J: O" C7 |0 Z0 m: [$ d. L( n3 [2 K8 W# V
- e7 g! D+ M9 Y0 A& S2.判断是否达到隐藏条件
/ q8 N' ^/ p# Y8 v" M8 c r. K) P9 e; o. {
很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。& |3 \8 Q* s" |( E$ s
private void dockedForm_LocationChanged(object sender, EventArgs e)
9 v" M- g) f, j+ {3 V0 k" l. n {
/ t/ q/ X- }1 a/ d. ~ this.ComputeDockHideType();
4 t3 |9 ]) H6 h7 ^$ f4 ?' e if (!this.IsOrg) , T" i; T3 w) S% z+ E. |7 t
{ ; S7 r3 C6 c) J8 z( e9 v
this.lastBoard = this.dockedForm.Bounds; 7 S5 m& a4 J& Y9 M) ~3 D9 P9 @6 T
this.IsOrg = true; : [& {0 H* b( ^* v+ _2 o
}
# d, d+ r7 L, W" f0 p$ g } ( d& Q, l# J; v: t
: x; B$ N' f, V7 G /// <summary> i4 c, L; Y1 E! i& f" K8 V
/// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。
1 \% u( a# J1 e! l$ j8 t* [ /// </summary>
) S z& y, J. ~% m8 i private void ComputeDockHideType() ; l6 Q1 F, L; K. @
{
2 Q0 F1 i- e1 Y1 h% K if (this.dockedForm.Top <= 0) . V- D1 n' W0 S* o2 t
{ ( ^: I+ p. Y* ?" k \
this.dockHideType = DockHideType.Top; ! e4 _7 u9 W- `; t# j+ h
if (this.dockedForm.Bounds.Contains(Cursor.Position)) " K R' ~$ \* X9 D* ~5 N
{
* ]. N3 }8 k# _6 s8 O; U% p/ t this.formDockHideStatus = FormDockHideStatus.ReadyToHide; % S: O3 n- p5 w; d9 a% }" G
return;
8 |/ R$ F3 U. d' \5 |4 {. S } , a4 E2 E5 Y( Z! _
this.formDockHideStatus = FormDockHideStatus.Hide;
2 j+ k; W! Y: L h3 Y! W return;
4 C8 ]& p6 F$ z0 X4 }$ {$ s } ' t3 `2 N( p; z* D+ c. o
else ' A. d4 t' I9 c# S. R V
{ L9 }' s8 x- n
if (this.dockedForm.Left <= 0)
! N" S7 c( l) y# W" R$ h' H {
' f- X E# |& A( z7 W9 C) Z, L this.dockHideType = DockHideType.Left;
3 w2 [$ O7 n8 q7 _! i4 f if (this.dockedForm.Bounds.Contains(Cursor.Position))
' I- [8 Y' s' m) p) \" f; Q# t: c8 ~' Z {
. t8 v/ {4 Q3 f4 w8 f# W" I; W this.formDockHideStatus = FormDockHideStatus.ReadyToHide; # B: U( n. u0 a
return;
; V; E* Z* E0 J e* ~# m } ; c0 m8 F* n/ t, K" ~9 l4 Y9 t8 }
this.formDockHideStatus = FormDockHideStaus.Hide;
/ E$ W$ z4 {7 _" g return;
* B, G. D! y+ n0 l- C }
0 z# L" n$ H- c% Q! t5 m8 K9 C8 Y else 4 Y: `+ z) K$ T U
{
7 q" t3 c( W: k/ w0 h- x if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width) ; t \, Q" T* }& L
{
. W1 T) u- z/ k8 v8 b this.dockHideType = DockHideType.None;
, s4 l! k; m1 Y6 h3 [8 Z this.formDockHideStatus = FormDockHideStatus.ShowNormally; 3 M; Z- M$ Y+ s2 F7 M/ ]' Q
return; 5 e& l5 ?$ V5 o/ ?0 a; e
}
9 Y8 K' ^% J1 f8 C+ y6 L this.dockHideType = DockHideType.Right; 3 k6 f3 B# M! \; Y" l9 I" \/ l. U
if (this.dockedForm.Bounds.Contains(Cursor.Position)) 3 P: D7 v8 T, @
{
" v% q! L- a$ V. Y) m4 D: _ this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
2 n( h; V9 q; e3 { return;
c! ^( _6 ]6 f# K8 Y% r }
$ x' v; z+ S. x# s9 j) h9 Y this.formDockHideStatus = FormDockHideStatus.Hide; " m% Q1 J+ ~' |5 b" F
return;
% W. @; c2 [1 ?, O } ! ?8 g' G; v" |! L
}
7 ? l" I1 B( B+ p' y7 x7 z, U }
1 k/ C" p9 l. K9 p复制代码
' ^, `% W3 n5 ]! Q1 R. o+ _& C# y+ D) x8 @, d) _4 u! e
! h" X. d6 j$ q! U上面的代码主要体现了以下几个要点:% |6 |) \1 e2 p; ]
# P8 n* C4 [+ M7 W; ?
(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。
, { N4 Q& ~' Z(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。
, p) H) t$ s& M9 E$ T2 a* y$ F( A(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。
2 ^8 y% b0 U5 g. b& l$ ^$ ]8 P详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。8 Y* p8 W/ E& u' l( k, Y+ y- a
+ l. h% G# j& w6 f0 \6 p
3.定时检测满足/退出隐藏条件
4 A+ Y. d7 G+ n7 u0 O: T4 q
R, e4 n' P# C: I# w( Q我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。
) I' o3 `5 } H8 z2 t/// <summary> 3 ]& a( a* J- t( s2 N5 a5 t
/// 定时器循环判断。
4 W* r: O# a5 {# i9 P /// </summary> & N) T x' e+ A$ {1 p" f
private void CheckPosTimer_Tick(object sender, EventArgs e)
+ l3 b4 d7 Q. G( n1 F; o" W4 f# y {//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外)
; M/ l! C- B1 D/ C' D @ if (this.dockedForm.Bounds.Contains(Cursor.Position)) ; |4 H2 E$ D4 G0 ]9 p6 J
{ if (this.dockHideType!= DockHideType.Top) # [9 F7 J. m$ m) ^3 I. Q2 W
{ 6 k- U& ^$ n' x5 Y$ i3 m
if (this.dockHideType!= DockHideType.Left) . g6 l4 N1 |, m7 u
{
- c# @4 ~% j# O. e/ C* u% y6 S" \ if (this.dockHideType!= DockHideType.Right)
+ E2 ~# _- z# |4 H* @ {
+ ?$ Z/ ^! r9 u# M5 E% F* E: ~4 i' c return;
. d1 _4 h& ~8 o3 M% `2 z! x) r: u }
' f4 G- P9 K9 z2 x) ^" j' z if (this.formDockHideStatus == FormDockHideStatus.Hide) & s5 }1 `7 a+ r2 M! M4 t& d
{ & ^# R% }2 F+ N: e" |: n+ M6 G4 q
this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y); 8 J9 \, S9 M" S# V$ D4 r: {1 U1 R
return; + m# ]/ H1 y. ^, u) X
} 0 z6 B9 |0 A2 L0 I& C
} ) n; k- h) o" H1 X# M W$ g
else
# Z. `, ~8 j; n# k4 a% J { : t6 Q; \# J$ n i; R' v- h
if (this.formDockHideStatus == FormDockHideStatus.Hide) ' o0 }! p. [1 c+ G3 }
{
: D9 H" W& ?- k/ v this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y);
$ v8 ^7 R! H' r0 w# E( i' J return;
7 Q0 h: I5 N7 {& ~4 W3 }# z } / l0 m0 r$ z' s G V. {2 Q5 o: H
}
) V: t0 }" S/ ^. B3 A } 4 S7 @6 ]: N- j& r: ~# L
else / ^3 K: N: B" Z8 T5 s
{ " c, {: x! W$ M, h
if (this.formDockHideStatus == FormDockHideStatus.Hide)
7 `. T+ K# Z) B3 S, m1 k" y {
( o/ S5 A1 I" Q/ u3 z0 I; X+ k this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0); % S8 j8 P) E6 U6 s/ a
return;
/ K- T& X/ E U/ r3 ` } 0 I9 @$ W5 o* ?* c: W1 V$ r
} # w6 J7 ]2 b$ u1 u
}
) @9 g# Q* w9 H' m7 m# S: @8 j else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。 - k2 _8 i" u; f0 j6 s! `
{ switch (this.dockHideType)
6 G( T. }* L! b4 f2 A1 | { u1 Y7 t2 _' e
case DockHideType.None: 0 L, _. C# h0 A- L5 _+ c1 ^
{
0 A7 V4 s0 y4 ?% W5 {; r) { if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally && ' t: F [. }! m2 f* t9 f
(this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height))
% Q4 `% w( R6 S/ ~- K { . ? O- @! I0 o3 L7 u7 f1 o
this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height); * q5 D: n' c, G' [& J! ]$ V& |0 w
}
/ I/ d5 D0 H% Y" e [5 e" s" d break;
& x) A) K) i, q4 S, t4 n& B6 {6 H! j } 5 i' S" o: K4 ^ Y' y
case DockHideType.Top: + }& o+ N8 U m8 p& h9 f
{ , z& q7 {5 T/ C4 h2 Z) E3 o) ]
this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1);
" l8 k* l5 ]- A8 l: d& E& W return;
4 f6 ^/ _1 M! M9 u# S+ m6 X } % u- Q2 j2 @5 w& p# w* V1 ?! P
case DockHideType.Left:
: p+ [6 a, A$ v0 H" Q9 G+ o7 B { 8 w; j# K E; H4 c
this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y);
8 M5 c9 v/ m! U# Y+ H' ~+ z$ K# c return;
4 V" d0 B" e% v7 \ E/ p, f6 [. P- c. o }
4 Y( t, y+ p M* E7 ?; X default:
% ]7 f7 _2 y3 A8 O/ { {
1 c& f7 {" @& L7 E& U7 ~" u if (anchorStyles2 != DockHideType.Right) 8 x0 p: |- w- Y4 Q+ a, Z( I: [3 H
{ % E9 a, ]: t; p0 ~& j. n
return;
1 m- ^, E$ E( i7 b/ A; A }
2 {6 n& ~2 M6 J: l% t this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y);
- L) i; ~8 T" x, Q/ a. U return;
3 s" ^5 H: O# [: u }
3 H2 L6 x! Q( c# B! G }
0 p9 p& R$ k& a- N9 A4 O: R } / A0 N% f4 P4 ?
}* B) K* T: d: X* b& K9 v: P# c% C
复制代码
5 W! \# v. Y/ _, b& d7 [" B x1 x* ]
2 }0 y+ x, |0 N Q+ {% y
P0 }7 C, w2 i M(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。
; ?: l ^$ b1 V* O(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。
I, n. @9 Y! {( C1 @ K(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。
: X' L1 ^8 w. _1 ]
3 v+ v. x# x3 n. Z三.如何使用AutoDocker组件( b9 \9 ?- m- u- A' l
2 o! v7 G) z, [, q" H' kAutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:
1 K& o0 j, ]# Y5 g. a. u9 o从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:
# S% u" R/ m( ]6 R! t) ^! Ethis.autoDocker1.Initialize(this);
5 _1 l4 ]# L( t$ [ T复制代码4 \- Q9 a8 {' _$ m( i& \ m1 p
这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~7 Z. b; R9 ^! e" t9 @- a6 D
- n, O% s: h$ ]0 L5 N
|
|