★我要吧★

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

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

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

6 \! }+ p1 y! }( R6 [) w6 P( z
! W( x, m0 f, {$ R" n, F* I/ I! w
( P5 P- s2 w" r1 P2 @1 ]我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。7 E- H7 A+ ?/ H7 k* S7 X
那么,靠边隐藏功能到底是怎么实现的了?" M$ U4 x# Z4 N8 \8 k1 l) {5 o

; C  P0 m* U" g8 a0 G" ~& u' q一.靠边隐藏的原理
$ u  j1 t& m1 N6 C% [% B6 j; l
靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:
& L  c8 N9 o7 |4 ~' R  q方案说明如下:& P1 B* O% N) y3 ?+ a/ S
(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。
$ r( C& i/ D. {8 h0 X(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。
8 S' X) o) z/ \1 k% ^# d(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:
/ f0 C/ v) J" L     a. 当鼠标再度离开窗体区域,则又隐藏窗体。
- Y! l- @# F2 F6 R     b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。: b/ ]$ {/ |" b0 H

: X4 T. h/ y2 g' V1 {+ L! g! N+ D二.具体实现过程
0 r1 n( B! [# c! z
6 y; x3 n  a. j1.基本元素定义$ V+ T9 L) S% A( N. A
首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:5 R0 V3 l) A1 b, R4 ~! q
/// <summary> & K6 [4 T2 D5 {; l+ g7 y
/// 靠边隐藏的类型。
: ?  d/ [2 U& g. P# |% e/// </summary> , q; z7 ^) e( s% a3 \/ N
public enum DockHideType , p  Z& a3 R% Z, `
{
: f  P& s9 d( X% V: D* D* y# E* P     /// <summary>
) B8 M; l" Q, A* f2 t     /// 不隐藏 ; w* T0 I& ~" c9 k# h8 ^0 q4 u# S8 z
     /// </summary> ; f1 B0 @& W+ Q2 {1 ^% V4 Z
     None = 0,
6 [7 ^+ [0 n1 C     /// <summary>
5 j8 c9 l9 G# O3 `7 e8 }2 Z! @     /// 靠上边沿隐藏
# W5 y/ r4 f+ w* Q( c2 X     /// </summary> - z; ]7 C3 F5 T9 @" N1 d; q- @
     Top,
8 G4 I$ G; r8 \     /// <summary>
3 J' W; x3 Q. E. _2 R     /// 靠左边沿隐藏
; F' e  I) G: G* [5 u, N     /// </summary>
; y& K' F0 P+ N. f$ K+ n. W     Left, ; ?# q8 m$ E2 b5 |: R7 ?% l
     /// <summary>
! @7 l# I7 Y5 x9 ]6 [     /// 靠右边沿隐藏 2 G6 a, q1 j' c% C# N, H8 X
     /// </summary>   E) T9 G; q% e' D9 J
     Right
+ B( ?- b% ]# \}
( T% `: t8 t( C0 U; [, h% g复制代码
; ]; Z+ U/ u3 R. N7 K1 d% k& i6 y! K0 U
: d# T: K3 f% ~& ]" [5 d其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:( y1 W9 }1 Z) ~/ l- x( W- {
/// <summary>
/ X" E, v! G5 n" Y% J3 f( O    /// 窗体的显示或隐藏状态 2 P) Y, m+ L# p+ ]% z" T( \4 z+ a; a
    /// </summary>
9 Z  Y& g. B9 y" E; h0 c; r    public enum FormDockHideStatus ( m8 B+ F' u# `( E$ c# o9 o7 H
    {
. l* B1 Q3 H; @3 y2 o        /// <summary> ( @5 w7 J' A; f& ]* f, Y2 a
        /// 已隐藏 8 y: U8 j  r+ W' G) B: F5 e
        /// </summary>
/ g2 Q* ~4 T5 ]* ~0 _! H        Hide = 0, + {- m0 ^/ H* I( t  L! N$ Y

3 }; A5 P) t* `        /// <summary> 0 v; U2 B+ _* g0 a! g4 {
        /// 准备隐藏
; Z( L, G1 x, `        /// </summary>
% P: T2 }/ i* j0 |        ReadyToHide,
) e) ^' ?3 X& W- k% ?( D" @/ x
+ b( V7 ^, w# i' N0 U6 w        /// <summary>
6 e8 O. C1 z, i8 G" w        /// 正常显示 3 `" J% Q. Z" N5 m4 p/ [
        /// </summary> 9 {6 E" G/ K% [8 [9 K
        ShowNormally
/ E$ p( E0 ~- h. D6 }. O' |    }# N4 ?/ @& O2 l. a- n4 B
复制代码
3 N  R+ k1 f* i
( _; b+ |3 G! N' @7 a$ f; {2 o( D7 A/ |# t, m
2.判断是否达到隐藏条件
  c. U. Y$ G4 m1 T! N
$ r3 e( p' L- a1 n% \! n5 C很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。
( W$ U" ]; i8 ~1 z% q# t, F) r1 uprivate void dockedForm_LocationChanged(object sender, EventArgs e)
6 u+ `" J. o# B        {   D3 X2 ?! k$ h6 W' a3 i2 ]
            this.ComputeDockHideType();
) [, Z0 ?# {8 f1 c$ Q4 e            if (!this.IsOrg)
) E0 T, M# @- r            { 4 ]  t7 p+ C' ~% i0 B
                this.lastBoard = this.dockedForm.Bounds;
' F9 e% \" T, l4 N8 C+ A                this.IsOrg = true;   e; v7 p6 x/ o6 `
            }
& p$ F, L; r) i        } : D7 y/ ?! }* j8 D6 X: m
! z+ {; f) \0 n! T8 Y
        /// <summary> & u0 `  D) a  V) h1 W# ]
        /// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。 6 U8 i" I* Q! |" }8 w6 j6 j
        /// </summary> 3 J9 `% c7 D& A  {, f8 T  a5 k
        private void ComputeDockHideType() 6 B4 h5 \" _, H4 o' |
        {
0 V# m9 b' Z' C$ Q# D$ T9 C2 |            if (this.dockedForm.Top <= 0)
* G8 `/ n: H0 M            {
9 _+ Y8 ?% Z& ]1 t                this.dockHideType = DockHideType.Top;
0 m" X" O$ N5 o% \. B                if (this.dockedForm.Bounds.Contains(Cursor.Position))
0 A+ Z9 a$ j; ~                { ' S9 Z% U, o" o- m! R) Z' I
                    this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
5 G1 S, i3 Y( a/ T% R% _                    return; 4 b% S: o, s: J+ ~8 a
                } + `4 i1 V8 I; {
                this.formDockHideStatus = FormDockHideStatus.Hide;
# {, }% v& E6 a1 @# r) G                return;
8 q* A8 j2 b' J8 ^3 {; j5 T            } , U, C) n) I/ g* J3 p+ O5 k( ^
            else
6 h. I2 F. n: V! N            { 0 C& m( ^# ?" R8 p4 V9 j
                if (this.dockedForm.Left <= 0) ' y$ C: T8 y+ v9 B. X; E- v
                { 5 y; U5 a. j! ^7 t
                    this.dockHideType = DockHideType.Left; ' c+ b  }4 V) C% V
                    if (this.dockedForm.Bounds.Contains(Cursor.Position))
  o7 |6 ?; V* p; b: D7 K                    {
+ Z3 Y) D) N2 R+ k9 l, P                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide; ! |2 r0 x! a* V/ M: G$ s# Z7 s, p8 S
                        return;
& H, ~9 l+ V0 Y5 v                    } # U0 h  L' ]* n& G
                    this.formDockHideStatus = FormDockHideStaus.Hide; 8 G  Q6 p5 G5 q' _
                    return; , y; a+ ]4 C; Y% A% S( f
                }   v/ @7 I$ C" k) G* }/ U0 M
                else 0 {/ o5 X# C1 }5 R- ?: Z
                {
9 N4 M" h) C+ c9 Y$ U                    if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width)
3 U  r( W$ ~0 y& \6 u4 O                    {
5 l5 E5 U! D" I) u/ h  B5 }/ S                        this.dockHideType = DockHideType.None; $ s3 a1 b( i$ s1 U" E
                        this.formDockHideStatus = FormDockHideStatus.ShowNormally;
7 {- c* Z, c* ~$ _- A                        return; 1 }" Q. n* C8 @, x! y
                    } # z. b0 f0 |" R. p8 c/ C. O8 t
                    this.dockHideType = DockHideType.Right;
% `' ?4 K$ l4 m( d" n7 r4 R                    if (this.dockedForm.Bounds.Contains(Cursor.Position))
) o$ K3 \  c0 v1 q" q                    { : f4 B. c% Z5 f  n! x+ V
                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
% W3 c2 J. w" l8 e0 P                        return; % p* B1 D) N1 Z1 p9 r" W* \8 U
                    }
2 B$ S# y+ E# [3 G                    this.formDockHideStatus = FormDockHideStatus.Hide;
- ^/ \# o$ l2 s( A7 V, L                    return;
( M  [" m: @  p% a2 k0 p; ?  M3 M                }
$ u9 c: ~" g; \- z7 r            } 6 Q, z( m$ L) A2 c! s" r
        }
$ I+ p+ I. z8 P, n+ P复制代码$ U& a1 _5 A5 b" D1 {1 Y) U6 @( j

/ B$ H! O$ H: n( p* S$ \& e
$ w8 }8 A$ |- B. X; p$ D: o, N' I上面的代码主要体现了以下几个要点:' u- j5 M% B9 h) |* c/ H; C
* q2 b( @0 W4 {1 B
(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。. n3 p' f8 F7 }- U. ~6 l2 \
(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。
- m5 E0 u5 d% W# h8 d( t3 H: b+ ](3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。
. W$ i) C  c; t详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。
) Z: W2 G3 ^" I0 R$ l. s* S6 @: n$ ]. \$ A5 Y5 b7 ?
3.定时检测满足/退出隐藏条件
9 _" l7 V3 E2 |. J* g( x7 F1 ^' r" D7 j) _2 V$ X( n9 z
我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。5 i- ^1 t* f! V/ l6 x( i
/// <summary>
) i) g5 r: Q  t( J) z8 J        /// 定时器循环判断。         
+ s- {# E4 E) a        /// </summary>        % v- J6 x, t, [; G9 M: h. Q
        private void CheckPosTimer_Tick(object sender, EventArgs e) 7 e& S0 `; z) c6 p7 }2 ^7 S
        {//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外)
/ A% k8 V$ X" S# N9 P            if (this.dockedForm.Bounds.Contains(Cursor.Position)) $ z5 |6 q. k! B! K, @7 Q9 C7 P4 x
            {                             if (this.dockHideType!= DockHideType.Top)
# \# p( z. a3 ]                {
' b+ x- ]3 n* Y0 A  y( @4 o3 }                    if (this.dockHideType!= DockHideType.Left)
% H6 ~' w. X9 u/ ^& G$ J: ^" U                    { # U' Q! T2 W4 d* U/ d
                        if (this.dockHideType!= DockHideType.Right)
0 \2 ?$ Q( J. |2 M+ E$ B/ d                        { & ]$ X, N. f7 q( b" _: o1 K4 d
                            return;
+ }2 N# c  m7 \) d/ u                        }
7 e4 M. R" H6 S                        if (this.formDockHideStatus == FormDockHideStatus.Hide)
/ K8 h6 v! x7 S8 E3 o8 k                        {
& R0 N1 {  U) T4 b# K" b+ e" T                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y); 9 ^/ B4 X) A$ B5 b+ J2 }
                            return;
. ^0 @" y1 e0 |7 q! \                        } ' L5 W& L8 N! E6 U2 j0 z
                    }
9 Y' K) ^+ ^; D! u, p  p                    else
8 K+ p3 w5 F" E: c                    {
* x: t4 p* c! O- {- ]9 M                        if (this.formDockHideStatus == FormDockHideStatus.Hide)
2 B: c8 J* j$ z) ?' W, O                        {
( V: \' W+ K7 l( y( e$ l                            this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y); 9 p$ A- j, j6 J  b+ O
                            return;
& [1 E  N) F) T5 k5 g% ?9 s' j& O                        }
. _' r! U2 G; @/ X/ o# h) W; v                    }
8 M7 W2 H* U# z" G. \0 D                } + H6 V9 v( }, D( {  D0 b5 E
                else 1 K7 {1 x- o/ q! y( u1 ]$ A
                { " J8 \, N4 Y$ p, n0 S9 L  P& ]: h
                    if (this.formDockHideStatus == FormDockHideStatus.Hide) 3 w2 r# {- O4 G" H0 K
                    {
! _0 E: \7 B1 I                        this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0);
" }( \$ L0 J& M* {                        return; 7 J4 m% D2 E8 l3 S
                    }
$ i; Z. A7 F! t% Z                } 7 Z/ A2 V* P9 q; _
            }
0 `; v+ ~) r0 I8 c! H6 V) T            else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。
( P( F1 _; j( D7 G$ `; ?. S            {                switch (this.dockHideType)
* D& y3 D4 q5 }/ {                {
& B) T. p( x! C: y                    case DockHideType.None: / i7 v$ X( J& ?, Q/ j% h7 d
                        {
) k: j1 p. e: `0 F8 J                            if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally &&  ! Y% V. }$ J5 T0 w, |1 m
                               (this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height))
4 D2 n3 _% s" l5 y) `8 W, q6 \                            {
' q2 j* `1 A4 E$ [) ?9 [) W                                this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height); 3 c& c& Q2 ^  q& J
                            } 4 Y+ |$ C. X+ B& I  w, Y0 q% J* D
                            break;
; X0 f: F8 s) H2 O                        }
  C0 C0 k$ U6 n, Y                    case DockHideType.Top: : u5 G4 N  m/ f4 ~  X# p
                        { # i- i0 M+ Q' t" T! `* i
                            this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1);
  V6 S- ]' ~! O" x2 m; i/ }                            return;
- B6 P0 @7 R: |9 p                        }      
/ _" M; D3 T7 o. a9 R" I8 D                    case DockHideType.Left:
2 z, B( }3 l! m0 W2 y                        {                            1 |* Y2 y) V8 F) }8 R' w
                            this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y); 2 r: W. ~5 J9 H
                            return; . v, V; S# ]) g" X
                        } & Q- g+ a5 p- a; H
                    default: * j4 g# d7 x! F
                        {
0 b9 O9 p4 _2 O; D. f                            if (anchorStyles2 != DockHideType.Right)
3 k* v9 R: y  D2 v. T( G& F                            {
" x7 h8 o/ ?; V' [  t                                return;
9 r1 t1 j+ a0 \0 Y3 E9 i1 \6 a( t5 r                            }                             . u; l+ g  {% O7 d9 k9 x6 m* A
                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y); ; V: b9 q5 O* o- Q+ M
                            return; 9 L/ @* n- g+ S
                        }
6 W/ K9 P! ?; F6 R4 |                }
( V7 S4 F! T% B- A% ?+ N( k. J            } % F5 ]* h; e  }; l
        }- {8 e0 B  p: i" E7 I
复制代码! m# \: g7 ~' F

7 X& y+ R( S& s+ H- }' v! \7 a
* l& l" P+ V8 ]" e1 b# G: V, V" M# j(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。
: U$ M- ~: Y  z* i6 L(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。" x! ]* A7 j9 o" ~
(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。' J7 x6 ~3 O8 `& Z8 s6 X/ j; t- @
2 P3 B, I8 [4 T& R+ @7 Y+ g% i
三.如何使用AutoDocker组件
1 X9 d  T: b0 ?% ^2 g6 k* O' M: _; T$ ^  n6 o
AutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:: |& d" W; R: }5 C0 Q" f5 H
从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:
6 C5 ?8 b5 D- E( }& {* g5 athis.autoDocker1.Initialize(this);( W8 ]/ H% X2 e- N& b2 Q) e) G6 S
复制代码6 s1 w4 Y2 C! G* Z$ K
这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~
5 o! k, c% i6 W3 Z; v4 c/ d6 \3 \* q) d7 [$ Q! O2 |* v
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

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

GMT+8, 2024-5-15 02:04 , Processed in 0.061616 second(s), 18 queries .

Powered by abc369 X3.4

© 2001-2023 abc369.

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