★我要吧★

 找回密码
 注册[Register]
搜索
qq空间相册密码查看为什么登陆后需要激活无法注册?

[技术] 简单三步实现QQ窗体靠边隐藏

[复制链接]
发表于 2016-2-14 09:08:05 | 显示全部楼层 |阅读模式
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。& r/ p) }6 i; l* k' F
/ Z; S; @& W* U3 Z  r$ H8 B" _0 m
. V% m0 z. x6 S8 N
3 P  f, t  f6 D9 T' \: {, }+ y3 a
我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。
' U# g8 \1 x# d, J那么,靠边隐藏功能到底是怎么实现的了?
9 j: [& y. Q5 G; E9 L6 m+ V+ ], ?
4 q+ x3 Y0 w6 [一.靠边隐藏的原理
7 c6 k* j$ m! b* \. }5 M4 j8 k! w
靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:3 F7 A# f+ |- o! |4 w
方案说明如下:
# {' K$ \  d( I! e2 f(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。
7 W6 G6 M/ b6 U. ~(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。
+ C. U( J9 ^3 M! S, h0 R(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:/ \5 c. @0 }; t0 ?! [- }
     a. 当鼠标再度离开窗体区域,则又隐藏窗体。: `' H0 j6 _3 z' S5 w4 D# n
     b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。; h" J4 L. I# U1 e$ E" ^1 ]& r

: \, }* m% l, u( g6 ?; B二.具体实现过程
4 v; S! y! q. w9 G* i+ y
' D: m+ k% _; _2 }- D7 _# H5 F1.基本元素定义6 q4 m! }7 S' e! R( P% J+ U; I
首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:6 l: M* z; {) l% T( R: X
/// <summary>
2 U. j" H! {& u/ m# A' s+ X/ V/// 靠边隐藏的类型。 1 c: P8 M5 u, q. M
/// </summary> - }4 s$ Q3 G+ l7 b% f7 ?
public enum DockHideType
* f+ x: k+ U% `{
6 G" r& d- L: ]# \     /// <summary> ! D# U4 U, ]& X: M3 Q. c( l5 ^
     /// 不隐藏
# W: d' I& Y$ N% ~! a0 ^     /// </summary> ( r! T- e% r  L* a+ W$ _
     None = 0,
/ m; K8 F0 ]* |, {0 d     /// <summary> ' d2 D" ]; g3 E7 i- z, e
     /// 靠上边沿隐藏
3 y3 }0 P  s& j* j     /// </summary>
: _, r( x0 U* E/ o1 t     Top, % {' k' ?- n( H8 t1 l; i* W, |
     /// <summary> : V+ [2 u- T( ]5 z7 a/ i( \
     /// 靠左边沿隐藏 7 l* R8 N- Z# M6 A6 g. O4 D
     /// </summary>
4 Q* L& j7 T6 I& c$ _! @% R$ Y     Left,
  [9 x, C$ d, h! z     /// <summary>
: p$ [% q6 z! Y" L     /// 靠右边沿隐藏 / J9 W4 G5 W2 N( O
     /// </summary>
8 p- e! u' N) ]9 F5 x) k     Right 7 G) B; u4 l/ x; Q. f
}
. p9 f0 w6 {& g( |7 c6 e复制代码
: f) P* ]' H2 h/ F! @
- X# V" w$ a0 n# q% l其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:
, m- X. \+ e0 P4 U& E4 i; {/// <summary>
. _, @& q/ M3 n- M# W1 ~; J0 |    /// 窗体的显示或隐藏状态
- b% t# b4 k# ^( e& z    /// </summary>
9 c- C3 @" E# h- N) E; I. B    public enum FormDockHideStatus
, }7 U2 V0 d, s3 c6 U% E    {
1 B% c% K3 v9 K1 r5 T9 ~; [* f        /// <summary> & ^% {7 Y# j% _" I2 `
        /// 已隐藏
) f2 v; G  p2 b1 A        /// </summary> 7 z$ ]3 U1 {. v3 ^; d! u
        Hide = 0, 5 X" |& b, V( s3 Z+ X: c8 @

$ H4 Z- r6 V: V& P        /// <summary>
. ~/ _' a. o5 Q' B9 l' _        /// 准备隐藏
5 {6 m0 s( j: O; O- Q! x        /// </summary>
2 }+ U  z0 U* `8 e0 v  \3 [        ReadyToHide,
7 t9 K$ Z8 k, D' v; F5 y) e  Y' h" t: o# v! F" \
        /// <summary> # X, |1 `8 t1 H2 {
        /// 正常显示
# i# U1 ~% d, k% U        /// </summary> $ k% ^4 l- G2 x
        ShowNormally
4 `) [6 v/ W6 B9 t    }& f; \4 S1 m5 v0 ^3 r
复制代码
8 a. o* G5 L  g1 ?4 W4 g. g% k2 \+ J' N3 N: y: Q: G8 [" e
; Z; W5 N. m- M- s) c* v& p) u5 k( ?
2.判断是否达到隐藏条件
8 @. q; x' N4 \0 y# B2 O8 M  y3 i; i! p' A" w; X8 R
很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。
, r  Q# f9 z( J: [private void dockedForm_LocationChanged(object sender, EventArgs e)
3 }- k% X0 Y+ Y+ a4 @- a        {   K4 x/ e4 Z  w* v; M- }# ?. L
            this.ComputeDockHideType();
' i$ S- G3 s. L" V1 z+ I            if (!this.IsOrg) : `* @: _  l# ?0 d' L. k# Z
            { 2 H5 N4 W6 X0 d* E  y' J
                this.lastBoard = this.dockedForm.Bounds;
) I5 y& E5 s0 n( A8 Y                this.IsOrg = true;
9 i7 h- b- B/ ^7 y0 p            } 9 _8 E! c8 O+ U- Y7 }
        }
2 J+ `0 n/ x- j  L( C0 u
+ U" b4 w9 B: F) z. h  L' g        /// <summary>
2 v0 X9 Q. K3 ]' o7 k        /// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。
6 G0 o+ M1 }: }" y: Y5 P" N9 a# v        /// </summary> . ?  o" Z: J4 s. ], s9 J
        private void ComputeDockHideType() " F& I/ `, [2 `8 @3 E
        { . d3 p0 [' k8 J: Q  R8 }
            if (this.dockedForm.Top <= 0) 0 `( a1 d5 x9 g4 w
            {
/ ^4 H. I8 G/ J8 q                this.dockHideType = DockHideType.Top;
3 _, e0 B9 h: @1 f( f2 ]                if (this.dockedForm.Bounds.Contains(Cursor.Position))
$ j* z( e/ M) v7 B/ \6 j/ m2 S  p% }                { 2 G; ~' M  b% r
                    this.formDockHideStatus = FormDockHideStatus.ReadyToHide; 5 j! o, x+ [1 F% {
                    return; 5 [' X8 M* }. ]  z8 |) }9 W8 A, R
                } $ H) i/ J% T2 j  n/ Y& f; ]
                this.formDockHideStatus = FormDockHideStatus.Hide;
$ Z& f# ^' m3 r1 u& Q) o( F                return; ) `: g/ }0 \# P6 S" F, E
            } 9 A' m1 @2 L1 _/ M3 g
            else % F% w, R1 L* G) i3 w; `$ A
            { ( O& w: ~- V% \
                if (this.dockedForm.Left <= 0) 7 P* P& D$ V7 v
                { 3 M# O5 \3 M! s; B! ]! u" a
                    this.dockHideType = DockHideType.Left; ; x9 f( j# r3 I9 D. {- h
                    if (this.dockedForm.Bounds.Contains(Cursor.Position)) " ?3 H4 G( _7 t/ D+ r7 q# I8 n& _
                    {
' N3 R; h% g; J$ [                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
* O3 ^/ u, O- |* T7 M2 A4 u                        return;
) J; o( `$ i/ v  O- q+ f5 }3 e                    } $ N, v* [8 [' P! B/ k/ _7 E% W
                    this.formDockHideStatus = FormDockHideStaus.Hide; 4 O. O$ l, c3 t
                    return; % H, j2 b! p$ u! [: w, r
                }
7 f" ]4 n& K/ K& Z, O, n6 |                else ) _$ B  ]9 c: [- O; A2 m
                { 2 U7 m9 ]7 i5 z- D. N  V$ m6 `" k% i/ c
                    if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width)
. h  h1 b, `) z9 v& h                    { 5 {. \9 [. k6 E. w1 |# }
                        this.dockHideType = DockHideType.None; ; s4 E4 @) H% I6 I7 `  I" z! M
                        this.formDockHideStatus = FormDockHideStatus.ShowNormally; , V4 U$ f0 k6 Z9 y+ H$ t/ x- ^
                        return; : e! h0 }# I' j2 d
                    } & {) w7 a. N3 n6 v: K1 x
                    this.dockHideType = DockHideType.Right;
7 E9 g4 }' k, y                    if (this.dockedForm.Bounds.Contains(Cursor.Position)) 6 i- i& p$ G0 V$ Q
                    {
8 x% i+ I* v; A6 q: E                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
1 F! Q9 \  x$ I1 V                        return;
$ y$ p. M: ]5 ?2 j; N                    }
$ |+ v- j  p6 W4 w7 B9 v9 U                    this.formDockHideStatus = FormDockHideStatus.Hide; 8 [" C4 c0 z/ b0 X
                    return;
+ q$ F! C2 h; b+ K  t# n4 L                } . l7 N  b% A& t+ a8 C4 u8 i
            }
1 c+ A+ P- r+ |6 {4 {5 ?, n        }1 \5 B+ {  x1 t- r2 U3 @5 ~' n3 j
复制代码
9 s# w! _& V/ |- j
7 C. o7 f6 R4 l4 m, H' ~& L, S1 k4 i4 P6 x6 W; F( @
上面的代码主要体现了以下几个要点:
) }, n. e9 ]# _  ?+ p+ G8 |0 v2 ?  _* f3 D7 C
(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。
  g6 R5 \$ r( e; w7 @( Y(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。7 N& c' D! U' S6 g
(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。
- D* a: S; h2 |! \7 b详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。8 g0 F6 u$ m( r  |/ _2 L
! ~- V3 J9 b5 v) m; ^
3.定时检测满足/退出隐藏条件& @/ O" p8 h6 _6 o

9 _4 {& a$ K9 q2 c8 C我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。3 q% s# _: Y- Y% U/ m1 W
/// <summary>
( U7 w, H4 u' ^( q+ P- ]$ g0 j$ J        /// 定时器循环判断。         
  H4 Z4 I$ H. V$ V+ X# o        /// </summary>        : k8 ?% P' _$ U/ g. |' w: {
        private void CheckPosTimer_Tick(object sender, EventArgs e) 7 X+ z7 |# ?1 t; F$ I$ ^
        {//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外) : {, \* M2 r- g. ?4 K
            if (this.dockedForm.Bounds.Contains(Cursor.Position)) 6 D# c6 ]$ R8 s0 Y$ r0 c
            {                             if (this.dockHideType!= DockHideType.Top)
! ^: B6 J( f0 X6 w) V# s                {
' ]4 y, r, O! e/ a" B) M                    if (this.dockHideType!= DockHideType.Left) & f) {: ^8 n! _" a
                    { ! F9 B  y+ k+ b0 L* @! X% V
                        if (this.dockHideType!= DockHideType.Right)
8 R- r+ y% F2 s1 a                        {
) b1 Y: a" `5 C4 T+ T# T8 p                            return; , U; U0 |. \  J9 b
                        }
/ f% I& ~2 X  p& c, O7 F8 B                        if (this.formDockHideStatus == FormDockHideStatus.Hide)
8 P1 |2 y! q3 D: J( Q# q                        {
$ j0 y8 b( N" Q                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y);
: {9 n( u; r: a- n0 y                            return; ! h) ~3 [* W5 ]& e
                        } + R6 t& q! w) z9 M
                    }
& r  C! m6 h! |( a" A" s0 @2 B                    else 1 w  `& D2 o) ~" l
                    {
- p' w1 i: |4 _% w3 Z                        if (this.formDockHideStatus == FormDockHideStatus.Hide)
5 {7 y% q( X8 }                        {
0 r4 {: S& @. |                            this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y); # V$ X6 ^2 D0 t; @# r" q3 n4 Q
                            return; " l/ J9 [/ [. M5 W( [3 I& `
                        }
) v3 Y4 g$ Z: h$ v* {* Z3 U) Y$ d3 ?( U                    } % |  ^, g$ ~' P  Z
                }
# i# ]0 _; B/ z. j5 S- x4 V                else
$ |$ Q% H! P: K8 c& C. \                { + _' ~3 r7 _# Z- J! ~
                    if (this.formDockHideStatus == FormDockHideStatus.Hide) ) I! A5 \" {! L% }7 V' `! t. p
                    { 6 r- F, _6 w$ y* i8 E
                        this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0); / a$ U' @- n8 y% ~' U: c) s
                        return;
! a; [8 [7 ~8 T  B                    }
9 y% Q9 ^( T7 S                }
7 d! ^- s7 d& C7 D$ I            }
0 p" r4 ~# }  m" a" Y9 s            else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。
+ W" t+ O' F2 I0 A            {                switch (this.dockHideType) 2 }- y6 h+ ~* C/ d* v
                { 6 H! S% g, B/ b) x2 \; R
                    case DockHideType.None:
! `9 V* Z) E( L: g                        { 1 l7 x  O; k& ~! [: W3 P
                            if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally &&  * D  G/ X. j2 n: w- v9 C2 a7 Z
                               (this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height))
/ [  Y" ^$ g' N; S9 \1 h, Q* m                            {
# j' y+ a6 D1 R) S1 M9 h                                this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height); 1 [) M* j4 V  ?) Y3 U  O
                            }
' P4 L% K- n9 @4 [                            break; + [" I& y" v; G9 B) Q. i( E( S
                        } 2 {& z. L+ ?: n/ l, r
                    case DockHideType.Top:
5 T  C7 [" V% H' A1 B4 E) r                        { 0 q, S7 |, V  W: K
                            this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1);
6 {) r4 T3 Q: U                            return;
; \5 q1 t! x1 A                        }       , a# |  v, I- t! Z$ N
                    case DockHideType.Left:
( L. f( V2 n! |4 Z( V- r                        {                            % m7 w* t5 n3 o+ _# \
                            this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y); 8 T2 x# r0 o0 O, x# X
                            return;
( ]. h/ E+ ?3 x                        } + I1 e7 K; S3 h) A) C
                    default: 7 e9 N3 H) j5 D% T6 C. K
                        { / R$ S5 z; f3 G7 N# L: M  L; h; S
                            if (anchorStyles2 != DockHideType.Right) 6 ^: l- m' H4 R
                            { # b9 q$ p" l( l/ I( p% n& T
                                return;
) r- o8 P# q2 v                            }                             
2 [, T" O- I" Z. u: d* }                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y); # w! [- V) @3 X' v0 O
                            return; " Y' w- L' ?) y1 z* x6 u
                        } " g3 _3 |' @2 M1 @" n, B
                }
. J; M! e+ T) @& _* h- Z. }& A) p            }
) d$ |# H' E8 j. a# p        }
$ F4 M7 `$ B& F7 J复制代码$ S- A( y* a8 y  {" q
# n0 m: u/ g/ I. |/ v* J
) M& f$ r7 r( d3 L5 O2 B' A- b6 F! K. k
(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。* b9 I( Y" i1 B/ Y
(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。
, A3 A1 H0 c- v$ }( c3 @/ z(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。
2 S( @4 _/ B1 ~* Q# `: E; A8 k
" ~1 S+ J5 {: p9 s6 e三.如何使用AutoDocker组件
1 g. I* E0 E; G; q, N/ a' T  D3 Z6 A+ [+ V1 L
AutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:
5 n0 z$ ]' K# W) G# X从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:
* N. r. n. u* x! }! Kthis.autoDocker1.Initialize(this);
4 G- t7 J- M# `3 |2 M' k复制代码
: u& {8 o1 r5 k+ j" m0 d7 t. y5 v) Y这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~
' S5 X. }" Y# Y, F7 U6 a' P! ^
- k* K1 v* P- v, f1 r2 T4 y% Y. U
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

QQ|手机版|小黑屋|☆我要吧☆ ( 豫ICP备13016831号-1 )

GMT+8, 2024-11-24 22:10 , Processed in 0.062902 second(s), 18 queries .

Powered by abc369 X3.4

© 2001-2023 abc369.

快速回复 返回顶部 返回列表