★我要吧★

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

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

[复制链接]
发表于 2016-2-14 09:08:05 | 显示全部楼层 |阅读模式
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。
9 l- C% o8 E% i/ z, E4 \5 @0 J8 R. O1 j
2 P+ g% ?& ~( m8 x$ q
1 F' w1 B7 p5 H) M
我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。2 o2 |* g# m4 F3 I4 t( T
那么,靠边隐藏功能到底是怎么实现的了?$ ^' Q6 v0 L! K* e1 a7 l, y
8 r) i9 c# Z# W2 G; s
一.靠边隐藏的原理
9 J7 s) \: M; D. M- @+ Z1 T3 d) |1 i
3 M  U$ Y. m$ W9 w靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:
& w0 ]" _$ z5 m  q方案说明如下:4 y4 v: U1 Q0 L3 }. T' Q% j
(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。+ W: o9 [& _& F1 R/ r
(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。
9 ]3 P  m1 ]9 W(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:
. n- J$ K# n! U     a. 当鼠标再度离开窗体区域,则又隐藏窗体。' f1 b! `" n1 \: M# `0 _
     b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。
7 G5 q3 `! ?! y8 k1 h  @3 e% \' G
+ L% z7 ?) r) F& M二.具体实现过程
! |1 `$ q" i3 d$ L# P7 R7 b
. b4 n/ ?6 I2 t( t5 C. Z" a9 U" N/ t1.基本元素定义
% h- m( P3 I' ?3 P1 X+ k2 c5 F首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:! \6 m3 j7 b, ^  I2 k
/// <summary> 4 j0 b3 n2 G# L" K: j
/// 靠边隐藏的类型。 9 \' C: s3 B/ x/ d, r# X6 E: Z
/// </summary> . Y9 ^4 p  _# B" P4 m
public enum DockHideType
/ j2 ^% l0 X/ p& W2 m6 I/ k3 P9 ^0 ~1 w' K{
5 k/ ^  H0 \/ n" \8 ]' b( _' j6 P     /// <summary> 0 T/ i$ Z4 t% m
     /// 不隐藏 5 k5 d+ Z6 [. T4 f  c, c. l: o; t
     /// </summary> ) d8 \: s4 {( O% N9 Z" L8 I! s
     None = 0, 3 a- Q7 P" N( N
     /// <summary>
; h4 {7 e8 o, X1 A: n' E     /// 靠上边沿隐藏
: v+ J- ~7 \' e2 z0 l9 i: m* u' g     /// </summary> $ V% Y/ t) c4 O5 `! V! d
     Top, $ `( T. h1 P; I
     /// <summary>
" b$ e+ V* d) j$ I: z9 p$ g$ O     /// 靠左边沿隐藏
, ]$ T4 `8 L; A8 X" Q) O. b     /// </summary> 5 `8 D* ~2 {7 T
     Left,
# P2 X' ~; B- ^: |2 x     /// <summary>
, c: R, n+ a6 V  q( y% M! x( x     /// 靠右边沿隐藏
! R0 c% C3 B  O: A' ?4 d! u/ Y& e     /// </summary> ' N0 ~, k. b9 _5 _: P6 M$ h
     Right $ v& q" A1 _' ?6 d4 u1 v; u- q
}
3 \7 |; X; t! l! m2 `复制代码. J) J7 B& e( ?5 F) {) O4 `: r

/ }4 s# X- C( q; Y' w8 N$ R其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:
5 c8 s: Y6 y5 P# a+ f# j0 X# h4 }/// <summary>
1 m& q, X# V! ~# |    /// 窗体的显示或隐藏状态
# L/ [8 B; j! X! U2 o0 p; a    /// </summary>
; Y4 ?$ b- D3 t9 Y- k3 @    public enum FormDockHideStatus 1 O$ P: n- h5 v" Y& l$ Q. _7 R
    {
6 @3 Z2 j1 y* ^$ {- n        /// <summary>
) k. v. L& i$ v* c+ L1 h        /// 已隐藏 6 ~: }2 ?$ O- p
        /// </summary>
/ U! A; K/ j; T! o: g        Hide = 0,
, h( ]. l$ I8 x$ t$ N! h  [$ v5 [. N
        /// <summary>
9 g- l8 S+ ?( c( [        /// 准备隐藏
+ w' Z; w- d" Y1 u5 b! {        /// </summary>
. d. p+ @& @/ z) Q  M        ReadyToHide, 9 p: t, q/ k+ D3 S: g

  r8 l0 h! B/ p7 \1 l& J" f* B. c        /// <summary> 5 C) D9 @  I- `: k) z! U! T
        /// 正常显示
6 @4 F- R: v* z* `        /// </summary>
4 C/ Q+ u7 ]+ O9 X/ v3 o        ShowNormally % `0 L5 q! T+ ]* _9 t( a" k
    }
% G. Z, j# V% ~4 h' y复制代码% X6 |* z" k) I9 ~7 ]' T, l
6 c, o% ?# m" K8 o. n; q

% H! \6 ?# D( y0 e2.判断是否达到隐藏条件5 B$ I) |( l) ?
9 Q5 n3 `* ]# F8 m
很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。% c1 Z) c: E, y8 h7 e+ R/ h
private void dockedForm_LocationChanged(object sender, EventArgs e) & o( n( l3 e" Q" M# a7 G2 |
        {
( W1 d% D3 R+ ?( f            this.ComputeDockHideType(); $ }6 ~/ X1 d8 q8 u+ e( o* V/ @
            if (!this.IsOrg)
1 E7 G% U( {$ s            { * [+ \8 D% @5 \2 T1 j! s8 \
                this.lastBoard = this.dockedForm.Bounds;
- m$ c6 j  Z  u                this.IsOrg = true; + I6 Y! d9 T' x8 ~1 i0 h+ u( ~
            } ( Z8 h1 m9 @$ ~& Z5 P
        }
( L& h$ R+ d/ R' [
7 [) L9 A' n1 ^9 H9 H& J        /// <summary> 1 W; c' T8 c( j0 d
        /// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。
* L0 x, S. O% I! D* ~6 Z        /// </summary> # R) [" ?# j. {0 l# f# X4 j
        private void ComputeDockHideType()
7 J7 Y( F. l7 @: \  Q        { 1 O6 `" g4 `5 r2 @
            if (this.dockedForm.Top <= 0)
* }/ R. f- L8 B5 ^5 S4 E            { & d9 `+ M3 V7 i1 O
                this.dockHideType = DockHideType.Top; " \: S, r5 Y9 u, ^) s! {; `
                if (this.dockedForm.Bounds.Contains(Cursor.Position)) ' L& z; I0 t, Q. @  I
                { - p: i7 x( R+ `4 [* O
                    this.formDockHideStatus = FormDockHideStatus.ReadyToHide; 0 Z/ T) S. d4 N$ |
                    return; 3 r1 P' c( Q2 C! @& B6 f
                } 3 p! r  V% }9 K! {6 |
                this.formDockHideStatus = FormDockHideStatus.Hide;
- K/ Z% q1 V$ u" R4 y( Q                return;
" P4 ?! ~# ^( V            } 8 A/ P% B. a# D$ Z* ^+ [' X
            else 4 L, L" ]0 ?9 V: C$ T
            { - K  {. K# x9 V8 s3 _
                if (this.dockedForm.Left <= 0)   G9 t7 D' P8 h
                { % i9 ]" f/ P2 C( n9 h
                    this.dockHideType = DockHideType.Left; - y/ Y0 k# x. n7 ?: B7 U* L( Y  g
                    if (this.dockedForm.Bounds.Contains(Cursor.Position))
# W6 T. }  e8 T/ U                    {
+ h+ b9 B/ g& A4 g                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide; & C. _2 n  c4 ]' u/ Q: R# R% S
                        return;
0 e( X; V" m2 b8 J1 \; [3 g                    }
: L- S& G+ p7 s# K3 R" r( F                    this.formDockHideStatus = FormDockHideStaus.Hide; 9 W! m# e0 H7 w; h( n% W0 Q2 W
                    return; 2 a, E, C  m* q. n$ g  ^
                }
, f$ _) ]! B# P7 R                else & A0 U& Z' F$ `( x* _3 ~9 Q
                {
. ?' W' X' ^$ I                    if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width)
# @+ ~/ H8 t& j5 t$ x3 X% b( L                    { # _/ S5 j/ {! z& o# c- g
                        this.dockHideType = DockHideType.None;
1 m8 I2 _3 X9 I+ r3 _                        this.formDockHideStatus = FormDockHideStatus.ShowNormally; 3 d% @4 j  I% w( O/ }
                        return;
( [4 r% l8 V# A/ u+ N# n2 w% @/ X                    }
  R2 J" e' N% x- ~: {' M                    this.dockHideType = DockHideType.Right; 5 j' ^' n8 p' I5 z6 N6 J
                    if (this.dockedForm.Bounds.Contains(Cursor.Position)) & T8 g, |! R3 H6 Y
                    {
& v0 E) m& P" P2 Q$ \                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide; 6 [& m5 }; j+ H1 p' z
                        return; 9 O- c/ {4 F& t8 t- y; J1 f! a
                    }
6 S5 u" Z1 h" B5 H                    this.formDockHideStatus = FormDockHideStatus.Hide;
- J0 H  e3 E% h                    return; * A* }. n( }9 D) i
                } $ J0 M- d; L2 p- }" p8 k" H
            } 6 J9 t' ^* A. a5 Q% m
        }
; p5 T1 G: V1 [% q/ D6 f% }复制代码, N! t5 K8 s5 G  T. v+ _1 x0 q

4 N7 G, O5 F) g. z
- O$ M; K$ k9 v上面的代码主要体现了以下几个要点:, a, x- O) B2 ^% U3 V
  H+ ?( ^6 W6 Z/ o5 r4 F+ B
(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。
" X* I, y- C7 N1 n8 w. G(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。
7 L1 X. C, i( Y(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。
: u. O9 c; J  ?# H详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。
* E$ h0 A+ `  c9 h4 O2 U8 c/ I7 Y/ B- f# U+ g2 G5 @
3.定时检测满足/退出隐藏条件- B. o% B* ~/ k; J% [  ^; h; Q
$ s& z& p. T8 T- S8 U
我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。9 h6 y2 h& B+ w. p0 c% s% Y8 W
/// <summary>
1 P. L- i4 \7 @6 }) e, k, Z        /// 定时器循环判断。         0 f1 R2 I# T& |# Y& R- ]
        /// </summary>        
$ a+ Q1 a# v  E9 q: Q7 _/ H) g        private void CheckPosTimer_Tick(object sender, EventArgs e) ( w1 Q. c5 n  g
        {//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外) + c5 d' K! ~# V- b6 s% M
            if (this.dockedForm.Bounds.Contains(Cursor.Position)) 4 V- A* P1 D, v) x5 g4 k
            {                             if (this.dockHideType!= DockHideType.Top)
+ V, o0 P6 A' N& ~! W                { ; j6 s; D, X9 e) k3 F
                    if (this.dockHideType!= DockHideType.Left)
, |' F* E+ h# y6 U. U8 u7 D% \                    {
! o" \: F* l0 o& Q4 o! a6 K                        if (this.dockHideType!= DockHideType.Right) ( Y5 |  F/ Y: }- N5 d$ k3 r0 M
                        {
6 I6 @9 L2 I* |3 g                            return;
$ _6 R: K4 D+ m' h5 J9 d. P                        }
, m+ s# G; @! F                        if (this.formDockHideStatus == FormDockHideStatus.Hide) # {5 u. A6 q3 j
                        {
4 J) Z6 L8 c) s# ~- D: x                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y); " q) x- ^; i/ z! p
                            return; 4 B2 y: A2 Q7 |, D7 }+ H
                        }
8 x# j8 P8 [% u7 v7 [* \0 w                    } " }, W) s+ W/ M+ r9 ^" Y
                    else
0 r4 H& D( d  g3 U, j                    {
$ n( s4 Q1 e: _4 V$ U                        if (this.formDockHideStatus == FormDockHideStatus.Hide) - z/ V$ ~* j" n8 K# k
                        {   I1 ~2 J! J+ {2 R7 @9 d
                            this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y); ; L0 j- C& R$ O4 K' H9 ]5 s
                            return;   d" f9 j$ Q0 |* P( M
                        }
2 L9 f3 p8 j' f1 j/ o- J2 h: ~                    }
& n7 C/ G& t) n! s7 `+ p- @; J                }
% D, s) z# _; d. t7 B9 I                else
/ c# I6 p6 Z2 B: M6 D4 S/ [                { # x3 w2 L% f( h& P) {
                    if (this.formDockHideStatus == FormDockHideStatus.Hide) - I- U8 L' n$ {: V- ]; L
                    {
- @% V) W. u9 b  m' `3 j( g- U1 {) ]                        this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0); 8 s2 z. M6 |  b/ N+ o% u5 @
                        return; 1 V  n, ^7 @2 n- ^
                    }
+ L9 @# t" u" }: M                } $ p/ i* U7 U+ R) O$ q, h
            } 4 j0 e" y, A3 B' ^# j  Q
            else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。 . C  U5 R4 n; `7 E
            {                switch (this.dockHideType) 5 K: e$ N) R2 [+ l
                {
* S1 R) {! W' y8 U# s                    case DockHideType.None: + `. }* s! z% [( M2 m' c1 ]: K
                        {
) ?& M: D7 |9 N1 E1 M                            if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally &&  
$ h3 v( i) [0 O/ s' d6 v                               (this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height)) 4 W5 [# M9 s) l' ?( X, t
                            { ; Q/ \/ N6 S$ U8 B4 x# {
                                this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height); # v$ t5 X  v. L- Z4 f& E
                            }
5 [& |4 B' j" r  O( i                            break; ) C( o2 ?5 V3 J. h9 _% U- o* E
                        } ! p$ W; E: P/ \: G6 r: ~$ W( {
                    case DockHideType.Top:
0 B- Y9 n# y2 X4 L1 X+ b2 n                        { ! Y3 `  f- ~! O: b0 k
                            this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1); 8 p5 L3 s3 l& }! i8 p  v3 j
                            return;
% v4 z: {: u' c( |* X( ^; K                        }      
* E6 t3 t& f7 _                    case DockHideType.Left:
: O! r0 j, A3 K) K$ P                        {                           
# @9 D6 u/ l% h' Z) `- k+ d% y$ F                            this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y);
, g5 M6 Y* k& ]                            return; 5 F7 K( u; u# [
                        } - q7 V* H) p) w- t6 S4 M& j" p
                    default:
$ f$ S5 g7 ~3 G: [                        {
6 u: S$ j  N7 n5 x: x% i# I                            if (anchorStyles2 != DockHideType.Right)
: r- F/ J! ?* h2 @                            { ; i" ]! }1 c5 Z! u& Q1 Q& ^. _- g
                                return;
2 ~/ s& n6 t9 v4 c' m                            }                             
4 d/ ^, j8 F' ]                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y); 2 z, J: u; E7 N1 ~
                            return; * S$ v3 n) C- ?1 N# s/ V
                        }
" `, n0 I0 f# ?: j# J                }
/ ]$ n. n7 m7 u1 R  C# x. p8 Z) G            }
8 H  _/ z4 p+ M+ N        }2 X/ P9 H/ Y4 t) B" N
复制代码# e3 m4 z% i! c+ S
: A+ o: o& x- i' b2 N6 ~
  R, L9 d1 K. j# r4 M
(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。; U8 Q5 }2 B! s) }8 ?
(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。7 K: H* v3 D) z5 y- C' W
(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。
) v# b( @" g. q; j2 e4 w# n# `0 N
三.如何使用AutoDocker组件
2 V* J) X" a2 w! C! r: M2 w1 E8 h5 m5 ]
AutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:9 h5 Y0 K9 ?0 E4 k! J% ^. u
从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:
2 O# M! ^, c2 g* d9 H' @6 Z' H  Kthis.autoDocker1.Initialize(this);9 K* Z0 D' Q( x. Y2 ]9 |
复制代码
6 u( _+ F" y+ V$ k) M这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~
! Y# M% v, D7 X9 ?
' @  g% v& P) L
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

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

GMT+8, 2024-11-24 22:08 , Processed in 0.064882 second(s), 19 queries .

Powered by abc369 X3.4

© 2001-2023 abc369.

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