★我要吧★

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

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

[复制链接]
发表于 2016-2-14 09:08:05 | 显示全部楼层 |阅读模式
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。2 n0 A7 V0 V  ]) K
& r3 e7 p6 O0 E( a" A$ {5 p

% ]8 k: Z- Q' U
+ }: c& I' h- g我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。
: q" `3 T% D3 N) ^+ P& o* e. n1 Z' K# _! V那么,靠边隐藏功能到底是怎么实现的了?# l3 Z) C( }1 B. P6 ^) \
  [& j  L% l5 |8 ]4 S7 I: [
一.靠边隐藏的原理
' G9 Q0 T( {2 u5 d0 }8 k! \; y6 ^6 m+ W+ _4 `; j$ Q9 {
靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:
, J  o. ^; O' ~0 O! L: L1 `方案说明如下:1 E0 J3 f0 F/ b+ R  r
(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。
3 }' `! W) v' u3 K5 d6 H  f(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。" l4 ?$ w; ]2 {. s4 Q
(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:
( h* ]1 F' i, p     a. 当鼠标再度离开窗体区域,则又隐藏窗体。
$ F* B  d7 e9 [3 w) t: n4 K" D& H     b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。8 i0 z  ]4 W8 |& Z1 p5 z
* E+ ^3 {" E/ F9 B* g  j
二.具体实现过程1 e- {! g0 P( b" O- d# D  z

9 r1 ~) h( Z% |- k1.基本元素定义# J1 b+ J* \9 M
首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:7 d; X2 Z8 z0 e( {$ S  c2 f
/// <summary> / B/ f1 _0 k$ Y
/// 靠边隐藏的类型。 $ z5 O* p2 s1 d; u
/// </summary>
$ q" N* o8 N2 G: Rpublic enum DockHideType
- \3 C, S! ]5 X2 H! U0 ?{ 1 H) g0 x  l# C) x7 N! v4 ?. B
     /// <summary>
, {. D! V. l9 m/ ~% r+ R     /// 不隐藏 * m  Z1 A8 l8 x8 U% L! r! U- F
     /// </summary> + z7 U3 p  W/ x9 j% x  v
     None = 0, ( C9 {' A) V( n# n$ w, ~2 a
     /// <summary> + H$ t1 C3 V6 K  ]
     /// 靠上边沿隐藏
$ P3 ~' E) @  U8 N     /// </summary>
: s  P% r  o/ o1 p     Top,
# o  }+ q$ r6 E: q) d3 Z+ f     /// <summary>
; \% d# M4 A/ s& q: o0 N     /// 靠左边沿隐藏 - q, P6 X7 l$ ^( Z" p
     /// </summary> ! o; H6 A+ ]  }4 ^
     Left, ! d( k& C$ [1 I- @$ D
     /// <summary> ; U1 y$ `; z+ I8 {' ]
     /// 靠右边沿隐藏
! d" V$ U& n" t5 r     /// </summary> 0 J! m1 u: l. H8 |) h5 _6 Z$ k; H
     Right
* d; o/ g* j. W; _}# w  a/ F+ M' o
复制代码& K3 U& h2 E9 P2 G, N

# _# [8 |5 r' @; F8 r# U1 j, @其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:
/ B+ ~5 t* S& X) ~. t/// <summary>
$ _6 v1 }; m- Y$ Z% R    /// 窗体的显示或隐藏状态
3 y. }* x: Y/ m8 |* ~    /// </summary> ' j' P! S* o% @! F9 ?3 }6 w
    public enum FormDockHideStatus
! S% c- G+ A- S; j    { , T' y& c" ^6 ~+ V
        /// <summary> 5 v/ y- V5 v) ?3 Z; e3 k
        /// 已隐藏
3 x8 A  ~* H8 [8 z7 V+ l        /// </summary> + C6 O3 w' c: m  w
        Hide = 0, ! o0 ]( N% j. j2 a  N

4 x2 b; I& i- |        /// <summary>
) M+ x' V9 }1 u/ t  }        /// 准备隐藏 9 D7 X" i$ ?7 `6 D* s! E
        /// </summary> 6 _' T  {  v) g6 ^' Y2 F2 l
        ReadyToHide,
7 }  M9 z( e5 L3 T8 P
3 R  O5 R' D- i: d        /// <summary>
' a/ o4 y) @8 o1 G, `) b, V% J& o        /// 正常显示 - S7 B5 t: r, Z. V( p1 N
        /// </summary>
) c* A. k* d2 ~3 e8 ?        ShowNormally
: b5 @2 x' ?, J  k    }
/ W. B2 A  r6 A% a复制代码
! L% Z  p7 G# G0 U( g0 p4 F
8 ~* k; E( @6 Q& b) ~, r+ ~: H7 H2 }
2.判断是否达到隐藏条件
7 T4 C+ Z5 r9 N$ ]' r# K
- t8 r6 M/ \9 l0 B: T% t很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。
  r8 T$ _- [6 T/ E) Mprivate void dockedForm_LocationChanged(object sender, EventArgs e) & a, [0 O/ a, c$ G/ b* N
        {
% i) y# U9 l! u  h$ ?            this.ComputeDockHideType();
- G" R  e) F* L$ U9 D$ o9 c7 X            if (!this.IsOrg) % W6 c4 h8 H' `6 A0 G9 ?
            { ; P! x, ]3 o1 R: T1 ?7 g
                this.lastBoard = this.dockedForm.Bounds; 8 Z2 N  v+ _6 l7 L2 g4 L
                this.IsOrg = true; / n) ]' l. `8 b; x7 n5 Y% B7 \
            }
5 l; p: o/ n! b' y4 B8 A        }
' \% M/ d6 a' C( t8 h1 c6 Q1 s
1 Z+ h# J2 X- i4 E/ e. H6 K        /// <summary> 0 y) d% B. c: N5 A% W& n& t% G, L
        /// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。
! s# O1 B$ p, g* m# d0 ~        /// </summary>
" c' F0 p: O4 G        private void ComputeDockHideType() , n( `3 G. K2 l5 M: Z0 Q
        {
9 V8 s& @  B$ r6 |            if (this.dockedForm.Top <= 0) 9 V2 ?: J7 L- P  R, k7 W7 N  {% t
            {
" @$ ]# p$ Y, [1 Z* t* @" y+ Q                this.dockHideType = DockHideType.Top; 4 h5 K2 Q; H; y7 g: w. p
                if (this.dockedForm.Bounds.Contains(Cursor.Position))
( ^% P7 Y% ?1 Y. N% R" {2 j: _3 T7 W                {
4 o: \$ Z, x+ O% q. W! W5 _                    this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
# x! \* E2 _: i1 k                    return; 4 j( T/ o# ?4 s+ i' |
                }
! p; w9 S$ W) G                this.formDockHideStatus = FormDockHideStatus.Hide;
  a1 q0 V: l1 Q: c                return;
% a2 g+ w3 x+ Y            }
* }; c+ B# n8 p- A6 E0 |0 T+ h            else
" V& Q# [. H0 I% ]# O% L2 d            {
$ s$ c7 s% U2 q; d; G                if (this.dockedForm.Left <= 0)
5 I5 F# E# u( _' ]% L/ q                { 3 f. I! O% t$ a8 v$ j+ J
                    this.dockHideType = DockHideType.Left;
3 ]# F- U2 A& g" C( i                    if (this.dockedForm.Bounds.Contains(Cursor.Position)) ! S4 |3 E7 I7 s1 `
                    { * ]0 W( l5 y+ \5 m3 E
                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide; / l8 Z/ b3 i3 T, |" Y5 x
                        return;
" T, O( n# d" g! t                    }
( ?2 b  k3 c: k& ]) r( j* {+ A3 m; {# c                    this.formDockHideStatus = FormDockHideStaus.Hide;
% c5 |" J5 p. c. q% a+ V; k4 G                    return; # Y0 e, p! O# J7 v! \& f) `  v
                } ( E: m9 y( c) A: _. m
                else
% \+ g8 f% r9 X- R+ w# o; I$ u* F                { + g% t/ }3 |# t8 z$ f. X5 g8 x+ X( B
                    if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width) * L/ C+ R- K, \1 l; ?0 m7 v" w$ M
                    { ( e. F' n' B* \1 C& B
                        this.dockHideType = DockHideType.None; $ p, A/ V1 c4 J& _- V4 j
                        this.formDockHideStatus = FormDockHideStatus.ShowNormally; 2 m& s. m; E) {9 O5 b7 J) U
                        return;
( U% P! K/ E, _- b$ Z6 W                    } 1 q& |3 S5 t# Y+ R( a1 S, {
                    this.dockHideType = DockHideType.Right;
& V. w8 n: Y7 \                    if (this.dockedForm.Bounds.Contains(Cursor.Position)) # q# f/ J  q6 M2 [& @2 K! d
                    {
$ R0 w  k$ j" h, Q                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
# }3 y4 F4 Y* i4 A" V4 Z& p                        return; 9 z; H0 p2 N! p8 I3 `8 B
                    }
, l+ w! e; O( H! C                    this.formDockHideStatus = FormDockHideStatus.Hide; 2 g  T1 b0 g0 z
                    return;
- y' L2 {' o  x! L# j+ L6 g5 i                } # ^1 s- _& |+ O: X% J$ w& L
            } / H% x+ v1 G2 b
        }0 T8 p+ u( A: A+ ~
复制代码
) G+ \/ _3 m! X5 j0 E
8 {  {) v: x0 A
" |' e; ^/ J0 Q上面的代码主要体现了以下几个要点:/ `, l( ]+ W* \3 R. h9 L: C9 g

; q2 v7 x* u5 b0 ^# ~(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。
/ `, _1 O# n: L  m6 u3 f  r(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。
! g3 M8 {' L# r, P; _# m(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。- P  a. T) u4 J: f) u. }
详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。7 M0 M& h- k! T# H$ Z6 a( ^; w9 n

6 s- t4 S7 X) g4 ?& z* O3.定时检测满足/退出隐藏条件' X3 h3 g. t5 o' ^0 D

( d! s, A% w0 G4 W9 g1 D我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。
' `* B+ o, ^. |/ v' u$ v! S$ {/// <summary> # N5 W4 K. \; w% H; g" B# B; m
        /// 定时器循环判断。         2 E8 r2 M; D0 ~
        /// </summary>        
2 s6 W, ~/ a* ~' Z! w        private void CheckPosTimer_Tick(object sender, EventArgs e)
, G" a- J) W" m/ L& O        {//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外)
( N- u3 W: K- _# N            if (this.dockedForm.Bounds.Contains(Cursor.Position)) 9 U4 m, r: E0 U5 X  r
            {                             if (this.dockHideType!= DockHideType.Top) + s7 B3 ^0 F% J1 ~- E
                { + }/ ^  V& v2 o5 T
                    if (this.dockHideType!= DockHideType.Left)
! o& A, O0 L+ t9 E2 q4 r9 i                    {
5 R& _. o$ s' l  W. F* b                        if (this.dockHideType!= DockHideType.Right)
, j, H$ z3 K* k* d                        {
+ S* W5 B  s, |1 k                            return;
( r" t; F% ~' F/ Y' H                        } 1 B1 n/ D6 L# k) I
                        if (this.formDockHideStatus == FormDockHideStatus.Hide)
/ g6 ?$ d; [( h6 D' V# f7 ^                        {
+ P  X; S! f  C( n                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y);
0 W9 \; N7 G6 b                            return;
+ G& G$ U1 ^, S3 c) S1 h4 o                        } $ s, s3 c' c' `. t  b2 H
                    }
. y7 H5 v; T9 h; m0 @                    else
0 W1 e% s) q% c$ A6 Z8 S                    {
0 d. N, n1 w% l1 y5 H6 l/ }  u, B                        if (this.formDockHideStatus == FormDockHideStatus.Hide) 8 C, n/ ~9 K- ?4 v2 G* S  Z5 S5 Y
                        { 4 ^* s4 _- H; p  k7 _  v, @4 b9 }% X$ Q' Y
                            this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y);
" U: u6 _/ m. J/ ?- t                            return; # P! Z0 l9 T2 E
                        } : Y0 Y! w3 ?4 b/ b
                    }
& u) H& n5 a+ n* A1 J                } 7 a6 W- {: q: m" d& a. t; L
                else
3 a: B  ~. G9 ]                { 8 m2 l& x9 ^/ r# ]. g
                    if (this.formDockHideStatus == FormDockHideStatus.Hide) 9 p# J4 P) `# i  Q; w" d/ ^! r
                    { 3 |  H( q% u" K' f- y
                        this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0); $ ?: N" X7 S/ D! l8 n2 P7 J
                        return;
3 c: ~9 a) ^, b  B8 H( v- i" Q  X                    } 9 g% ]8 p0 [* x
                }
  Z4 ?4 Q% D$ o# |+ p            }
5 o& e4 ?( Z, V5 R0 [1 B# F2 m0 z) @+ c            else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。
; G7 @7 ]) x4 F2 J- o# e            {                switch (this.dockHideType)
& k3 W) D$ N, b7 |                {
7 L8 G1 W* ?7 m# R                    case DockHideType.None: " q) K+ w" |# Z5 U+ v
                        {
$ d! Q  @1 d0 ?. Y                            if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally &&  
# f  @) `: T9 p- x! x                               (this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height))
5 ?7 z# B! v. S                            {
( G4 g8 q, x) ]  _                                this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height);
, Z* n4 e% x4 l2 P                            } ' k) i2 D: c. a" T% E
                            break; + n: p6 E, J) j$ w  N3 _" ^* d& ]
                        }
4 P% O- B  F3 x, e                    case DockHideType.Top: 7 k' l$ S  d5 h2 P( T# U
                        { ) h( f3 N* l" ?: a  Z% B' z2 q
                            this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1); ( i0 i) f2 A0 A. O
                            return;   s3 C% a% G) d8 }
                        }       : k5 T5 ~( a: U# V2 k
                    case DockHideType.Left:
9 F& I% r5 S9 F6 O" y5 o. C1 V                        {                           
6 T4 k% ?9 X, W2 l& c6 z/ A                            this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y);
5 D0 {  R/ L  K. e                            return; 1 \& o3 m0 a* x9 d* j
                        } , b& ]* ^# A) D5 W7 m
                    default:
3 g+ w, r  }' a* q0 b% F                        {
! `2 E  U  N0 V7 y3 o7 D5 ?, f0 l' o                            if (anchorStyles2 != DockHideType.Right)
# a2 b, Z# J- z- a/ w                            {
% I1 N/ U5 ?0 c% ^8 j  T% S                                return; % q# R- `8 \- W
                            }                             ( x! y2 @6 W- J* T0 ?; d2 o9 J
                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y); 2 }6 K9 R6 P; |
                            return;
' T& x: c8 _1 c2 a+ i/ Y                        } 7 I  z$ v1 }0 F* a
                } $ A3 h' p! X, L9 `$ w, ~0 L) E
            }
2 J8 \) V6 A1 m; b: T# H# {+ _        }1 d: V2 m& u2 K; x$ z" ]5 @- ]
复制代码' H( J: I! r6 z

! d4 v+ C, @: t0 f9 M. u2 a6 z" l6 `* p% ~3 H! A- m( J# }
(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。5 H2 n- G. U. t
(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。
  w2 D; i/ s- o9 t$ i) B(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。
7 k! U) Y" H9 n/ _( w" f2 D4 `: R+ H3 d3 d
三.如何使用AutoDocker组件
/ L( h, \6 }! y% q" B1 q
8 R4 o4 Q4 n2 ]& f! [! AAutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:
" i7 D& K- F% s7 x3 Z从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:  f+ Z% T2 n7 {: ^0 u& y* X
this.autoDocker1.Initialize(this);
! ]2 E7 z6 g$ p! A( W( N5 ?9 v复制代码0 C( i" B4 P- L: ~4 K$ x4 t
这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~
1 W% o# N( y  J! x' s  T; G* h1 I
. I2 g) F8 z6 a! o0 S8 b
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

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

GMT+8, 2024-5-30 18:53 , Processed in 0.067351 second(s), 18 queries .

Powered by abc369 X3.4

© 2001-2023 abc369.

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