★我要吧★

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

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

[复制链接]
发表于 2016-2-14 09:08:05 | 显示全部楼层 |阅读模式
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。' D4 ]5 e: ^" |

1 o$ a7 `& l+ w0 A& U* J$ I: X7 R* ]! t6 U" J1 C' }( H
: e5 ~7 P0 s, C- H! \
我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。3 o8 `, N* {9 ]+ V
那么,靠边隐藏功能到底是怎么实现的了?/ o- x4 S" B& s6 I# y$ C

1 Y( K# ?. {- g( ]一.靠边隐藏的原理# W8 o* G: j0 b6 b, Z, g4 G
" i& |. C, F7 H7 o. Y" P
靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:& ^# U# M2 {5 K6 {. \
方案说明如下:2 s% {+ u: V) S# R
(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。, ]# W; l6 E+ e1 g3 r
(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。! b" L" [4 q) I) W6 }6 c1 p
(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:0 W( l  o3 |# O, }$ n
     a. 当鼠标再度离开窗体区域,则又隐藏窗体。
5 p1 m( [% d% f1 w     b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。) `; e3 ]" y, W3 ]" D2 h
6 ^5 `' S! s, l' t3 O! `
二.具体实现过程- y6 q& h' q: P" t1 x0 a, V* W" L* J

* y" @9 c1 \) M, R1.基本元素定义
2 t* Q; r$ f8 T0 {8 y1 ?2 L首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:
" G) E* L2 u3 m2 I6 X' j, t# s/// <summary> 8 @7 S; Q- j# g. }" x/ h; ?9 M
/// 靠边隐藏的类型。
1 @6 u8 [. a+ U5 R$ i5 y/// </summary> 6 u: v* {) Z& y0 g; T
public enum DockHideType
& R* V& c6 S6 h6 `{   {8 m$ [2 M  ]" ^7 F: F" H- U
     /// <summary> 5 e0 X: Q. ~; P0 V
     /// 不隐藏 5 i: S4 K+ `$ a
     /// </summary>
: T% P- T$ C/ ~" C- O, X& e     None = 0,
0 p" y( ~1 }* g2 V+ e     /// <summary>
5 N$ w* A7 _- ]7 E$ g' e     /// 靠上边沿隐藏   ]8 r/ [1 y0 }% \2 N% k" u
     /// </summary> ( \$ `& U% R+ Y3 m9 K# p( I" A! B
     Top,
/ Y: `, z. x. ]     /// <summary> 9 B; ?9 V. g( Z
     /// 靠左边沿隐藏
/ T! _8 ~1 U; t8 u     /// </summary> . y3 s, e) x! i1 g: b3 G7 {2 ]+ L
     Left,
8 D9 y- `4 o2 ]0 @! h     /// <summary> 8 w% Q, R$ p  y! L! R$ `7 D
     /// 靠右边沿隐藏
, h3 s+ d& h" D( c% e& \     /// </summary>
8 p2 d/ i6 m4 o: Z( v/ {     Right / ^7 I/ u2 c' Y. W% O
}
  t* u  r# [; {8 d" F3 n! M  B复制代码
: O  P3 {2 e2 m/ g2 u- u
  ]) i4 U, W! t5 V其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:
! ~* p# N/ ^) l/// <summary>
6 H/ ?" p- w( g% T8 J    /// 窗体的显示或隐藏状态
. c% [3 d  w! R. f" W8 v* y    /// </summary> * }3 l6 ]; p  @
    public enum FormDockHideStatus
) H3 B# Y* P1 R    { 1 K0 W, _+ V3 B, u, r$ s
        /// <summary> ! T+ K* k: z6 h
        /// 已隐藏
: j) G  M5 F* K, o6 R        /// </summary> + U  i" l8 P! K" r
        Hide = 0,
1 b$ l' P6 f( E+ }" L# s2 M8 f' P
        /// <summary>   O; K2 a( h* G+ Z$ E# j
        /// 准备隐藏 + X7 X0 v' h& g, J* `$ w
        /// </summary>
: A; p6 @% H4 [' p, M% A  w* k        ReadyToHide,
8 l) }8 G0 |9 w; D5 v, w: z1 J  I2 c( w- Z; L2 z
        /// <summary> . m/ ?& x/ d' t8 ^. |# Z! T4 L& h
        /// 正常显示 * J* S/ ^" @6 X7 E+ I8 {/ H
        /// </summary> 2 ]: C0 ?+ M1 f; f! m3 b3 L
        ShowNormally & F* Z# w# E; ^1 D# ^* m4 ^( l( ^
    }
2 a' V( X8 _3 ~+ S8 o* ^& X* r6 h8 @复制代码
0 `0 N" F# R. V' o
8 P, R6 g7 J5 B! {9 v% r8 P3 B' y2 d  i: W$ r' P( x. M3 ]
2.判断是否达到隐藏条件3 i7 \: I$ g$ p. V
6 [, U8 L' H. h7 W. B9 H
很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。* {- D& I) J, z( U, d
private void dockedForm_LocationChanged(object sender, EventArgs e) . v+ s( r+ ?2 A9 i, `) N7 Z8 ]/ m
        {
: ^0 a9 l( _  f! z8 q            this.ComputeDockHideType(); 7 @1 t2 k8 w5 G) u: A- w) T
            if (!this.IsOrg) ; }% U: R0 U% }  L* i$ Y/ k
            {
* {* m( P0 h# j/ f1 E4 s                this.lastBoard = this.dockedForm.Bounds; ; l7 c0 F2 h- ^# H
                this.IsOrg = true; - P5 r% L1 F! c- w3 w
            } * T/ Q1 G2 X! e5 ~( j
        } ) W* g# M* ^) R3 I" `, Y

$ @$ i" `) x! I        /// <summary>
( C+ j5 [8 r( O% [* Z0 g2 y& x7 @( y        /// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。 . ]' x) ?6 L$ ?* i( E
        /// </summary>
) ]( x. a4 B: f2 c" N/ J- e2 Y& @        private void ComputeDockHideType() 7 g, x7 e6 N0 X% ^3 W1 S
        {
( e0 u5 K# K3 Q            if (this.dockedForm.Top <= 0) $ v) _& r4 z1 W3 G4 O5 H. [) P" N$ b
            {
, z5 S  q9 O$ s6 y% L+ C* ]                this.dockHideType = DockHideType.Top; : ^! z9 V& R# H$ w
                if (this.dockedForm.Bounds.Contains(Cursor.Position)) 9 b) x: T6 F. Y  {) g' L4 F! {: L
                { 8 }5 V! X3 D  s) B2 Y. r0 I
                    this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
$ ~4 `, l6 v# w5 J- M' y. J                    return; ' l; q' F, t3 ~& B
                } 6 b9 S& y# i) p/ h
                this.formDockHideStatus = FormDockHideStatus.Hide; ' B) h7 C; e7 E7 a, i  B
                return; 4 H7 |1 A' X0 z7 D
            } - k; O" K1 a3 m" Z9 A* v/ [$ J
            else
" E" E* v" a" k+ S            {
+ p+ l0 ~+ ?5 h2 d/ P' H5 O                if (this.dockedForm.Left <= 0)
, F# b; @: L0 ~$ d# s/ b                {
7 s# i7 x( F% v" e                    this.dockHideType = DockHideType.Left;
, E  V) b" ?* z5 c                    if (this.dockedForm.Bounds.Contains(Cursor.Position)) 5 v1 k* j( i) {! g+ b9 c
                    { 0 {; T/ M2 O; \5 q( ~, J+ x
                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide; . R4 O/ C2 e. L* Y5 h+ H' Z9 Y
                        return; + ~8 N4 R7 t2 X/ L9 |
                    }
& o( t# A' p# V/ E" n                    this.formDockHideStatus = FormDockHideStaus.Hide; 6 g3 O/ j; g% b$ u
                    return; / M' y0 k( @; x% I
                } , r. \: \; U" N8 s' d9 `
                else # a  O' b, @- n; R+ A
                { % e5 D( h/ a) F9 w# q
                    if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width) 3 }) v; q$ \2 E: ^9 c( B, F
                    { . ~* X" M/ W8 a! a8 z1 j6 V
                        this.dockHideType = DockHideType.None; 9 b8 C1 `4 J1 g: F7 E* G: t1 K
                        this.formDockHideStatus = FormDockHideStatus.ShowNormally; & e+ @$ t4 G  |1 N7 |
                        return; 0 F, D7 n$ k0 l& J+ `1 l8 y
                    } . G, v! L6 k0 S; L# r1 g
                    this.dockHideType = DockHideType.Right; 2 a' D- V, T* m5 F2 V
                    if (this.dockedForm.Bounds.Contains(Cursor.Position)) ( v; c: I, n5 Y- R( @7 d7 X
                    { 7 ?0 c0 G- _5 g8 O
                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
7 S7 N4 o9 H9 D/ E( j; O/ c                        return;
  n4 k  e) B1 j% u/ g$ v                    } " {$ ]% I7 R' a5 ^; x# k+ H# v
                    this.formDockHideStatus = FormDockHideStatus.Hide; 8 [  e0 I: }  S. i% z7 I# M! G
                    return; - Q% l, @: S8 E" {
                }
8 c2 @  N1 G2 Y% w, [6 G* ^7 e1 T9 c, W            } + Q+ w4 _9 E4 D: ~8 g
        }
0 @$ ?+ w8 I- z- S  j4 m复制代码
: v) t' g$ M$ ^; U4 D2 \
1 i8 }6 r; e' P3 t" m3 H, H) Z! t4 W% j8 d1 }& Y+ d
上面的代码主要体现了以下几个要点:+ T( ~0 x) v3 g

2 F$ X: H0 S& J6 r. M(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。6 s4 g. m3 z& j/ l3 R( z$ W( l" x
(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。
' [( ?) C( ?2 |4 d9 @0 K' z(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。5 T  w. e. f1 D$ d( |5 h8 U) E; x
详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。
4 q: u, b  n! B! a/ f8 u0 v' g0 u% V3 ]  |6 l+ t
3.定时检测满足/退出隐藏条件
: l! d% X# L' C+ q
+ c- q1 s! y* J" h; U. `6 A我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。
4 T$ N2 E0 a/ p4 Z4 v' a/// <summary> - Q2 h; O' X6 p3 z7 n/ |
        /// 定时器循环判断。         
8 p9 N6 P  ~1 T1 k: Z. |        /// </summary>        ) _! R4 z7 I  |5 J
        private void CheckPosTimer_Tick(object sender, EventArgs e) 1 C& {( N4 {3 Q; h7 ]2 x
        {//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外)
+ r% ^' \' \4 r. J5 z( [$ L            if (this.dockedForm.Bounds.Contains(Cursor.Position)) ' d% }3 Q& F" v3 u) B
            {                             if (this.dockHideType!= DockHideType.Top) 5 i' Y: O+ j) b0 f) F$ N
                {   ?: h* a3 [- |- h3 E. H& `: g
                    if (this.dockHideType!= DockHideType.Left)
: @$ [9 n5 i3 K3 q6 s, j* g' F                    {
6 t; j5 {: @: Q                        if (this.dockHideType!= DockHideType.Right) 5 r6 k# B$ x/ ?
                        { * E9 n  G7 z! k  F1 U# R
                            return; # D, _9 l4 P. G4 t
                        } . |$ y' V' T; b' ^; C& }
                        if (this.formDockHideStatus == FormDockHideStatus.Hide)
# O( G) ]4 u1 f) {! Y9 C                        {
+ i8 Z" {* ?2 l1 m                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y);
. m  K) D8 C7 W. W6 Y: A- l0 y                            return; ( G" n# x& m- n8 w2 f7 N1 A
                        }
/ P6 t) d# j3 p                    }
- N! B' N- e% {' Q3 \6 {/ y# |6 p                    else
, s# e$ S, i4 F6 g' t) \3 E" _8 c                    { 3 q# O5 c- O  C& b" }7 `$ ~' N# ^
                        if (this.formDockHideStatus == FormDockHideStatus.Hide)   u4 _; `( Y2 W1 m
                        {
5 F2 v! o9 r: @: |5 [& ?                            this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y);
# E- O, d, R* \/ L, {8 z                            return; ; p3 v0 z6 k0 d" P3 F/ M4 |
                        } . y& p9 p7 s% C& `% i; r) y
                    } / r+ e0 B# p% N9 c0 O
                } 9 C* }$ i/ G, w3 b2 x5 m2 q
                else ; U* @  H* m* w5 k- j5 z: @/ ~& o; w
                {
0 u6 g) Z' ]: o$ R- h4 R/ R                    if (this.formDockHideStatus == FormDockHideStatus.Hide) 9 I& _3 Q3 s# A. J4 w9 y
                    { ' ^! g/ E/ j# v, k& W0 N6 [2 _
                        this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0);
2 ?3 D7 \7 H; |0 u  M0 u                        return; % E# h8 f7 Y8 w9 ~; e+ ]9 O4 h. ~
                    }
, p7 h# h" p% l- U                }
# u' T( a: [4 g0 C: H8 D            }
( q: O9 a) Q- ~; J            else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。
+ }/ v* y& n# \            {                switch (this.dockHideType)
$ F+ J( }/ M& o1 a) k8 }                { " @1 F4 a8 q* `
                    case DockHideType.None: & `% g  W6 a; P- E
                        { 3 m& i- ^; c( A( T
                            if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally &&    l: C7 {5 [$ i  q+ E, t, d
                               (this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height))
3 ?  N0 j' q: ?0 L( Y" j7 a! q4 K                            {
8 ?. V1 g4 p% o, C: p                                this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height);
6 b7 o% @; C0 G                            } 7 G! s: Q$ a# o5 e' ]
                            break; - C' |" s$ w0 O- J$ s- U' N
                        } * m0 D, C3 w  R" f; K1 z% z
                    case DockHideType.Top: ) l( X6 b" q& l" d& d% i; p
                        {
& ]0 R9 M4 m0 O8 @. M                            this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1); % M- W( d- B; J6 H5 a+ |( ^) j
                            return;
2 m$ b; e+ F4 p                        }       + _, O- q- A, J
                    case DockHideType.Left: , t7 G; ^/ V0 p$ \
                        {                            - o) Y: j! S9 ~* |/ G8 O. B7 F" O
                            this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y); # k7 w! x2 S& I4 Q! r/ ^: H
                            return; 6 Y# t) k+ D* H! F
                        } ! {/ |9 J, W# g; c( o9 B  L) ^
                    default:
5 z9 o( [& D1 d; E7 Z                        { $ U" ?( @# [! Y  v: c! R! h% x) d; |
                            if (anchorStyles2 != DockHideType.Right) 0 Q2 p* X( q# w- |2 M9 f
                            {
* A$ J5 V9 \( I0 D) ~6 }                                return;
1 y% l& g. y) I9 z# d2 r# ~                            }                             
& u/ t' v) t- W0 L; Q# P                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y); 2 O) ~  D; A1 Z0 F! Z9 z8 h
                            return; - B4 ^. }1 v( r$ Y
                        } ; G! r0 s/ Q4 C' t6 V2 U% O( h$ U
                } / ?: |# z. Z# _" Z& |. Z
            }
( a; u9 F. x* G: i- Z3 C3 s) A% r* L        }1 I7 q) v4 \2 X6 `7 f
复制代码; t  b: {+ H5 i9 ]* Q8 D
1 g& }8 h. Q# c% O0 c$ |. f

2 G7 z* V- X% v(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。
, Z5 a, ^. ?: L+ ?# N& T* X( H/ C- p" C(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。
/ R; V/ c6 [+ c4 d& P0 k(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。
# D% a: y  ?9 b- F! g0 T
- N: P7 a- c' ]/ C三.如何使用AutoDocker组件8 i! K9 Y# G" ?3 A
3 h/ |7 t9 O$ x6 [
AutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:
4 }' y- }+ B+ R! x: V& r+ j从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:) g5 o' v. R8 F, ^4 D4 Q7 w2 k; I
this.autoDocker1.Initialize(this);8 v/ k3 o6 ]9 R
复制代码, x: ]* b! Z; U4 Y$ k
这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~
; I& a& U; `( Y* \4 m1 K: s7 n7 E- j, e  F! B3 J6 u% P
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

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

GMT+8, 2025-12-14 20:10 , Processed in 0.063417 second(s), 18 queries .

Powered by abc369 X3.4

© 2001-2023 abc369.

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