★我要吧★

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

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

[复制链接]
发表于 2016-2-14 09:08:05 | 显示全部楼层 |阅读模式
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。2 b; l+ S2 }) U# S+ F$ C, L
5 h/ m% G4 K, `. D; |- V2 `
5 B% }( L6 W# x+ W: ~/ ?, M

0 H+ v3 e1 \% |/ c3 ~5 i我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。# f" Z3 O# b  U1 o0 v# f5 w+ u
那么,靠边隐藏功能到底是怎么实现的了?2 w' k- M8 s5 j

0 K+ G0 g* ^6 _% ]3 A5 o, k一.靠边隐藏的原理: E! g. B+ m% i

' J0 e2 g8 d6 H  @5 b$ n靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:0 T/ X, D4 m7 e" O
方案说明如下:! ]! S8 b# w6 c8 R3 F: W
(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。# R: S* E! K: w- P2 b) K, n9 N
(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。. |" j' P6 _' s! e
(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:
8 n, C2 x& a7 c% v     a. 当鼠标再度离开窗体区域,则又隐藏窗体。  s% e9 B( k) o' p$ ^) k# g0 q
     b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。; Z7 Z* b) w1 Z3 i
* q7 n$ q% w+ C8 Z
二.具体实现过程6 z2 J! ?; G4 X9 K; f) ~* {+ I

: g7 L: H7 {. `& c: P1.基本元素定义
- w) _1 X- `1 L$ W首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:
3 }# E- J! i* n0 e( j0 X/ |/// <summary> 6 h8 D- B: i1 W9 {# W* Q" a& }
/// 靠边隐藏的类型。
( Z( F, {( v8 k) N/// </summary> ) J$ V2 _$ i) V$ E+ V! q
public enum DockHideType
: L6 G- [2 ?- i9 @; ^{
4 z+ Y' L7 ^: F" `& d7 A' U     /// <summary> 9 I3 T% ^. G3 N6 M  v
     /// 不隐藏 + @8 M; p% t. G0 w; F
     /// </summary>
' X0 i. P! C$ @" Q. b) ]- X     None = 0,
- n* G+ g- c9 h# |) S2 q+ R! K) k* P     /// <summary>   C7 K5 e/ G- H) J1 N
     /// 靠上边沿隐藏
9 ~: e, k8 q3 V     /// </summary>
7 C) V; D* ^3 v& G7 l1 D/ L     Top,
6 m3 y0 a/ U; j9 G7 s9 r1 t/ Q     /// <summary>
5 ^6 U' E9 V' N! a. T- W     /// 靠左边沿隐藏 : G8 i* s8 r5 y. I5 p
     /// </summary> : L( o8 [& n8 ^* o. p
     Left,
" Z/ y1 f0 {; [* o  [, c  a5 F     /// <summary>
1 O% x& d6 I5 F" \. Q1 i     /// 靠右边沿隐藏 # r' x" r/ I* B( x$ S
     /// </summary> ' H; s- W/ W0 W; `& B+ ^9 `
     Right $ m" c/ K5 k# A% P
}  O; F7 X' I/ }% L3 v8 V6 Z5 d
复制代码
  C& h; O; U% d. T' b! J& D; o3 l, s0 s2 d' o
其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:
; X5 ]0 z6 S" F* A+ w) n9 n/// <summary>
( u, N# y$ ~; e* F! i. d/ k5 G: L    /// 窗体的显示或隐藏状态 7 d1 e3 j/ e2 r9 H# O+ P5 u# U
    /// </summary> 1 \1 \) r' O; E" r; w/ e
    public enum FormDockHideStatus
# q/ ?7 L! }3 l    {
7 W, \% z6 V: C8 U        /// <summary>
9 t  L" r) }. U6 e% n        /// 已隐藏
7 e0 {' {; j0 J/ v. z6 c        /// </summary> 2 F, O, o9 |, c" e
        Hide = 0, ( W. s8 K( X* S0 X9 U4 E
  h& j% Z$ @) c% J6 @) C5 H* S( X
        /// <summary>
/ v" `6 M  ^) d2 ^- C- c  v% Q, d        /// 准备隐藏
" y6 @8 b- r3 Q! Y+ q8 {        /// </summary> : Z6 L6 Q+ |8 |4 H* O  ]& B. J
        ReadyToHide,
1 I: S7 }! ^' P4 X
. V3 S' l4 W! i$ k        /// <summary> 2 w1 P8 P, `8 m% M& v" }$ U! q8 y
        /// 正常显示 ! d3 Y$ Z+ ]1 a
        /// </summary> ; v2 J- Y( k' r6 z8 M# F9 @( n
        ShowNormally " d. f, r9 ]- o/ I. d6 r6 \
    }
& E) n) V6 H; l& k# R4 I: n复制代码1 d& Z" k, X+ a# v0 ~3 g# ?

# f1 @' m# h% m$ D* C  V
2 ]+ v1 b* N- W  n* w4 b) ~6 |2.判断是否达到隐藏条件
3 x1 l, Q+ T/ |) T1 _$ ], o" C' r9 Q! E$ b
很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。& d8 t' n6 Z; x! ?3 i2 D5 K
private void dockedForm_LocationChanged(object sender, EventArgs e) 8 f! l! T2 e: ~
        {
/ t  [( m1 a2 R! n, x/ l3 t            this.ComputeDockHideType(); ; i1 ^% p( Z" ]" [. @
            if (!this.IsOrg)   |$ r1 K+ l/ K& H  ~
            {
$ S2 o$ S' K) n$ x% H                this.lastBoard = this.dockedForm.Bounds;
- P  M7 }: _6 n" H1 ?5 L                this.IsOrg = true;
9 D7 u9 J9 h5 a# D/ P+ n" Q            }
- N) F( F) L$ u' p$ j, V        }
3 L: L: \3 J& x
: S  F3 d7 X' O# f2 @/ [, p        /// <summary>
% b4 v5 V5 K8 t: A* t  E& P        /// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。
: t) J1 I' @7 S, A        /// </summary>
+ u1 Z( V, Y( @! M2 W        private void ComputeDockHideType()
; h( j; d2 F1 m# v9 p3 F. a        { . C# c* K7 N! R# u& f5 }$ \. S
            if (this.dockedForm.Top <= 0)
( X- h4 B! x& I- p            { ; l' h' n5 T: ]. |
                this.dockHideType = DockHideType.Top;
+ y0 e! R' {9 ^' q' a6 ?                if (this.dockedForm.Bounds.Contains(Cursor.Position)) - r8 {4 p' r5 {0 \$ _! c( u
                {
$ {4 o* E# V& b$ _$ `                    this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
) Z5 |0 b, Q& J4 V                    return;
. t7 y5 I( g3 L* V( i0 |                }
* X, \' e2 S/ ?                this.formDockHideStatus = FormDockHideStatus.Hide;
: ^" Z1 t" _* F* R8 w                return;
  s8 x$ j& a' `            }   F- e1 }4 u0 r& T( c! C
            else
/ K/ j! a# I' ~) N; B            { # O0 j( h9 a# g- n! ^5 r! M$ B- V0 C
                if (this.dockedForm.Left <= 0) 3 f3 t) [# h5 `" y% c
                { / v. G: O6 O) |' z8 ~
                    this.dockHideType = DockHideType.Left; % m, x/ h* w! y- ~
                    if (this.dockedForm.Bounds.Contains(Cursor.Position)) 6 q0 b* d* S3 G1 Q; w- F
                    { 5 ~5 @9 Z, }4 u+ u
                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide;   `. }' I" n1 |7 Z4 _
                        return; 1 g7 _/ X1 A* x2 m, `
                    }   Q$ x3 `7 N3 Z1 ^9 K
                    this.formDockHideStatus = FormDockHideStaus.Hide;
9 K0 `: k$ D( C                    return;
# C6 d0 K1 K8 |% Z                }
! E' Y8 \. d5 W! a% ~                else
- F, F3 t! m  S                { 3 b& f9 h* D! ^  w
                    if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width)
$ x5 i6 b6 W5 G5 G% A; {" {                    { 7 `1 k: \2 g6 n1 u: c- E6 W$ Y
                        this.dockHideType = DockHideType.None; - L* p! B, Y3 d; D1 W5 s: V. [
                        this.formDockHideStatus = FormDockHideStatus.ShowNormally; 4 @9 @4 U) z* }! v8 E
                        return; * J0 [& q5 u1 A3 m, b# m
                    }
4 C/ ~6 o! ?% P+ b4 }                    this.dockHideType = DockHideType.Right; , Y! D. K4 x7 s7 q) j' R% {! L0 G
                    if (this.dockedForm.Bounds.Contains(Cursor.Position)) 9 A) d( K! x+ N- `$ ?
                    {
4 \; K( h3 s8 Y* z  ^8 W                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
5 y; B3 p  _- K" ]+ M- _2 `                        return;
3 {6 t' d$ g7 N* c                    } & E/ Z7 W+ E, T- [* _
                    this.formDockHideStatus = FormDockHideStatus.Hide; 9 Y' f/ z/ q9 ]
                    return; % j1 e/ C/ q1 U2 U5 a
                } 8 ~5 l3 i9 s& d9 J& I, S* ~" K
            }
& n' Z' |  S9 Z# @* A        }/ q* V: u4 m* i: m0 q) o2 a9 g1 r
复制代码3 O6 L6 [- n5 x- U* `
! I/ s8 f. P! z" Y& m
! f! z! b% t( q) l5 W: f5 N
上面的代码主要体现了以下几个要点:
" ^  Y7 o( [' |% r( A/ o" o3 m+ y( G/ H
(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。
6 @: e& p( P8 ]' R; P( v  ]! W9 c" d(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。
- P; h; E) r% ^+ p. t8 R(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。
/ W5 w9 L# E) H) o7 e5 x* h. k详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。: ]/ ?5 r) I  ?9 t9 n

4 u  F2 M: S" r" X3.定时检测满足/退出隐藏条件
1 K" u7 E- @* V2 B2 t) P, e
5 T) F" a' E1 i* ]( o2 s8 u我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。
: Z! @" `6 N. |, ?6 j' E/// <summary>
7 w9 G( N0 n. A# X8 H. [        /// 定时器循环判断。         ' b$ ?2 c, G5 n. r; w2 |- @1 a8 P% j
        /// </summary>        8 S$ A' s; u  p0 @* a! P. U
        private void CheckPosTimer_Tick(object sender, EventArgs e) 3 r" c5 W8 V2 t& _' u
        {//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外) & ]) o& a) W; Q- o  J1 G  o& U5 K
            if (this.dockedForm.Bounds.Contains(Cursor.Position))
1 v2 X$ b0 E$ a4 B            {                             if (this.dockHideType!= DockHideType.Top) 7 @& i( Z3 X6 T4 u9 Z1 T
                { # a4 a- f* S6 t. C9 _; h
                    if (this.dockHideType!= DockHideType.Left) 6 R- I0 T5 D# }
                    {
7 g, u- V0 C6 d! \/ U/ M                        if (this.dockHideType!= DockHideType.Right) 7 m- z  O- X! _
                        {
/ g) o& ~4 i: H                            return;
+ y) d, r( t' G( b                        }
2 N0 M4 @8 G6 h: y                        if (this.formDockHideStatus == FormDockHideStatus.Hide)
4 y; T  p4 U& b% K                        {
5 N; a! X  Q& S+ l) {4 g1 C                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y);
! e) v$ i" l- T. s+ i1 B5 m                            return; $ Q* F# Q/ q) P' }) ^, s4 D
                        }
. P; J# r1 p- N  |5 E                    } ; _* H; d8 ]+ v
                    else 3 |# T! Q9 m' ^# ]/ V
                    {
% R7 ^+ y. O3 v, F                        if (this.formDockHideStatus == FormDockHideStatus.Hide) 6 Z0 Z- s% Z) G$ s
                        {
# S6 l) O- {) J) o! C                            this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y); : D9 l/ x% X( }, X+ B' I. r
                            return; . e2 ~* e/ G8 e/ r) t; R
                        } 1 g- A0 l6 z1 q) U" P3 f
                    }
) _" D  b9 H5 ]. e5 i. n: C                }
+ F; g$ q! E3 _1 S) h8 [  r/ B                else
, H. Z8 s/ g' M, Z. @2 E! P! S& K                {
  ?: v$ \0 J9 P( R) j, K                    if (this.formDockHideStatus == FormDockHideStatus.Hide)
& l, X1 v& C# X4 g                    { / N& A  E* T- {0 V* U$ c
                        this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0);
1 P, U6 }- M3 a7 m. I                        return; 1 ]2 l! S  S0 u" ^. o# K
                    }
  S* I2 ^: H+ @. n                }   s4 D# t: v# l3 k) R
            } 1 Z3 r" ]# N& l3 g# Q+ t
            else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。 3 {" p  z4 ]- M# n6 Y7 [  s3 w
            {                switch (this.dockHideType) $ V7 w( [) e. e) z
                { & s* u2 [4 D) k" T0 v( Q
                    case DockHideType.None: , n! J! J, ]0 o1 S9 y5 s) c/ V; m. \
                        { 3 G  P5 B! z7 `1 ~! n1 F
                            if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally &&  
2 \1 z2 b- a( V  v                               (this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height)) 9 g4 T  |, G9 I( G  t5 p  y! U1 G
                            { " p7 m, D) _6 d) h
                                this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height); - i0 r* y0 t7 J. @1 K8 u
                            }
" }: e6 Y2 S1 a7 u/ R6 Q' v                            break;
  q4 }. q0 x$ @$ O7 v  W, z                        } , ?9 V. T# q. o* I2 o, `6 y
                    case DockHideType.Top:
4 Z$ @7 E  f% p9 z                        {
- C3 K4 k8 j* s1 z0 ~1 t3 n                            this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1); 1 D7 n& Q7 e. t% |
                            return; 7 `, f( a( R5 a/ n) {) H
                        }       1 f1 H  H8 ?/ f1 q! A2 |
                    case DockHideType.Left: ! k5 q* T" R+ T8 w7 ~$ ~
                        {                           
" \. v5 R1 k8 B0 M" E% e  M                            this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y);
" O  w$ L$ @6 e                            return;
' J# u( |" \6 @: q, a                        } ! E5 m, l8 X5 u
                    default:
; h2 a. `# R& H" p                        { * f4 X6 Q! d# {" ^+ f" k4 ]
                            if (anchorStyles2 != DockHideType.Right) 4 w" o. S* N' V. \! n( Q; A7 g
                            { 8 z; Y( G% x+ e( B: j7 D
                                return; # z' c" ~; y+ i& B# }
                            }                             4 `7 B  Q  G( g* f; a% E
                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y);
) X1 _/ S; ^2 i: ^: `2 L                            return;
1 \$ m% `$ g8 k- J& f: q( \8 v                        } : e! T% {- e# D3 F; @
                } 6 H1 o& C1 s; ]9 T0 F
            }
- A# Q) C! W9 N4 O) q. K        }
( s- }" A9 Y5 h: S复制代码
: t3 j+ S" G5 M, `' d' r
' @. K& w& H7 w2 x' C1 s3 I% r
* Q0 s( T* H) o9 X9 g% V(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。
& Z8 W8 s/ b# z$ W6 N7 B(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。& H3 Z/ j# y5 T/ @: V/ f
(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。
* I$ E( a3 {/ E& ^) }0 R& {3 k% B6 e( F5 Q5 x
三.如何使用AutoDocker组件( S! u6 b' w$ G/ F- A

/ O  |6 z( Q: H$ b# M4 y# b% H7 aAutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:
. ]  u4 H" I3 \- l从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:
' b7 \) R9 k$ B$ Y9 B7 Zthis.autoDocker1.Initialize(this);
  F5 g. t. i  Y- F* R- d7 y+ w& G复制代码
; y* r: U: u3 @: s0 u" ]  P% L) Y这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~
2 R: c3 L4 Z, ]2 U! M. j8 y% L4 `
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

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

GMT+8, 2025-1-6 05:49 , Processed in 0.063384 second(s), 18 queries .

Powered by abc369 X3.4

© 2001-2023 abc369.

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