★我要吧★

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

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

[复制链接]
发表于 2016-2-14 09:08:05 | 显示全部楼层 |阅读模式
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。4 F/ Q% b  `0 N& X5 T6 ?) [" r# y

# ]2 d5 f6 M" J! n: w3 {; s' ?1 a+ |1 s1 }
) M- h3 f) V  W3 j8 F3 }. H% Y
我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。
, H3 F1 h% l: L. B那么,靠边隐藏功能到底是怎么实现的了?
, x3 S; R2 s  b4 i2 {: c/ W& q
2 ?, d4 }: b0 ]0 k一.靠边隐藏的原理
) F! K1 i1 w) c9 `( C, i& p- x$ F4 Z+ P' \( {* w
靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:$ O+ l7 P& E/ M( @9 n2 W1 x& n/ R
方案说明如下:
1 k! o/ O7 s$ n$ }5 d(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。
, o! b1 G- V" X2 K; J9 N5 L(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。5 L3 i  k- o0 ]! Q5 t( W
(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:
/ O; l8 A2 R$ ]8 q; C     a. 当鼠标再度离开窗体区域,则又隐藏窗体。, x$ w" T+ c9 P4 r) i
     b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。
; J0 i) I! ~% m3 X5 J% t: W: G- r- ], y3 Z& g
二.具体实现过程
$ y9 n4 r0 l# C8 y- Q4 @& T6 }% t2 R6 x1 r: b1 C. u# m: m' W
1.基本元素定义
! w* o' [: K7 |8 R4 j8 h首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:/ }" P: U+ G5 [4 k
/// <summary>
0 V! k* l5 B* S! @5 B$ }$ n% I$ t4 `/// 靠边隐藏的类型。 9 T; e) p5 h3 V& C+ Q
/// </summary> % E8 J$ V! N, x" D) A- S3 S
public enum DockHideType
0 J0 n# R& }/ O  z" Q% p{
$ l9 K- p, m( n3 k2 v/ q$ y     /// <summary>
: ?6 c* N6 T! C5 U     /// 不隐藏 1 @, u' E5 }; v7 a; A' g. B- E
     /// </summary> / _, U" u. {- G! H) s2 \
     None = 0,
  p# R# g! u% Y6 X: A     /// <summary> 6 o! s7 k! Y/ G8 t
     /// 靠上边沿隐藏   G9 r, j2 V3 |+ T4 O8 ]) I' \
     /// </summary>
* O, n4 w" T3 h8 n  t) @3 P8 d& b( a     Top,
* F/ M  ?. c& w5 _% X7 v     /// <summary> # Z! y/ c$ ^1 f: R, P- J
     /// 靠左边沿隐藏 ; j- [& x4 L! z2 z- o
     /// </summary> $ ]9 G( _3 g4 ~% G3 D
     Left,
' ]/ m9 ]9 H6 y! n. W" J     /// <summary>
  F& {1 F5 y7 b, P3 z/ i     /// 靠右边沿隐藏
: e9 h' w; Q# h; A0 _; l9 S     /// </summary>
/ w1 v. B  L+ l     Right
# D7 K5 i1 j, t9 _  Y+ P3 m}
. s* S+ }4 k4 Q9 e: p. u复制代码
$ j1 ^9 i8 w8 S5 A" a
/ V& S6 c; y% P0 J6 F" x其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:, p/ ~" l9 B4 `8 ?- s7 q
/// <summary> 2 A1 D$ m) P1 M% A5 f
    /// 窗体的显示或隐藏状态
6 ]. D; T1 T8 u3 v3 X/ ~4 [    /// </summary>
. a# ?. z  l+ {& U: f2 h    public enum FormDockHideStatus
. k1 I; B- B/ t" c: {- b/ P    {
2 ~: }7 g) T! {& ?        /// <summary>
( y; |; {! x( y6 X; w' ]8 C        /// 已隐藏 # S1 F# ^7 i# l; {) v
        /// </summary>
( S5 V6 W4 B0 J2 }% G* [3 A" x        Hide = 0,
; u9 v$ z* L! A- v: w
; h9 T8 R4 Z# ?5 t* c        /// <summary>
" S2 e# a5 m+ q        /// 准备隐藏 5 u2 e; A8 n+ E6 }4 _
        /// </summary> 3 u% m2 A; H3 E0 J# {: {
        ReadyToHide, # j1 K- u# [8 \( I8 \" M

$ f& V9 g5 \* `( W. n& Q* z2 Q        /// <summary> 7 J6 H3 Y3 c: f3 h4 G
        /// 正常显示
2 h0 h) y6 p! G7 P) \        /// </summary> ; Z; [0 m9 s& d1 u9 _! o, F* A* q
        ShowNormally
6 i0 K; f6 H1 l- R6 }    }
5 U, ]2 ?7 ]# a% M- w* h复制代码
& z9 q/ q  H5 D- ~5 b4 Q: m  k5 q2 t/ J0 n6 A' D4 C8 f
) G3 _3 B% g8 ^5 M# ~" B
2.判断是否达到隐藏条件
/ t& D% U$ K  t! W7 q! Y4 x9 g! }$ D# \) K  R
很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。
/ \$ \8 K& i  T. H" R5 bprivate void dockedForm_LocationChanged(object sender, EventArgs e)
9 D& k8 h) F6 R6 v" r( T        {
/ n5 [4 G2 ~' f/ i6 ?% x            this.ComputeDockHideType();
" a1 _+ w% A6 Y$ }6 C. Y            if (!this.IsOrg)
% Z/ X, P  M: t0 x) m            {
+ Z, w6 n$ q1 ?2 M. h' W% j                this.lastBoard = this.dockedForm.Bounds; & W' `& x1 j! U/ x* ~. O: ?  ?
                this.IsOrg = true;
( t3 j7 u" h' M# ~2 a            } ( p4 [" D5 f& b# }5 H
        }
7 j+ W  h9 m8 v' _3 n) j9 D% i4 e. z7 r
        /// <summary>
1 Z9 ~8 S0 W! c1 @) G        /// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。 ' a6 J* [$ R5 Y: N( P
        /// </summary>
9 @. k% I* _4 `& K6 I        private void ComputeDockHideType() ( ]  ]. E/ t) L& n3 V# t+ n7 t
        { 1 C/ B+ K: W5 v* F! g/ f
            if (this.dockedForm.Top <= 0)
- O) Y: @, H( [* x& l5 `            { , m* q7 c9 U8 I- u! X
                this.dockHideType = DockHideType.Top; 2 |  s  t" [- ~) X3 I
                if (this.dockedForm.Bounds.Contains(Cursor.Position))
* u4 H# b! D3 ^2 T$ |+ D' V% [                { 5 y% o, S/ ^7 K2 r' C! s
                    this.formDockHideStatus = FormDockHideStatus.ReadyToHide; ) D& ]+ _6 u8 v* X: S: n+ u" H
                    return;
- J2 L9 i: }6 d, K                }
; D9 N2 F1 J0 @5 K- W                this.formDockHideStatus = FormDockHideStatus.Hide;   ?: V' F% t- S. q! I! F5 v+ U
                return;
" M# r; w( _, c" J2 y% u4 X            }
# j/ ^6 e3 S" y            else 6 B1 @3 H' p' s: }1 S; L: @
            {
3 o5 O& U. p3 M8 T0 ^                if (this.dockedForm.Left <= 0)
& W+ K+ M% X9 i                { 4 P( e; c9 ]* ?3 H+ M# I" k
                    this.dockHideType = DockHideType.Left; / V" V4 ~1 A* b
                    if (this.dockedForm.Bounds.Contains(Cursor.Position)) 9 V( z, M- O8 \9 \; Q+ \7 C" ]8 `
                    { 9 n# m6 U, {3 f. {. j% W
                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
9 g# r+ i  A; U  b                        return;
! G$ I# f! M8 l9 a8 b                    } ) o+ S- p1 {6 U+ z
                    this.formDockHideStatus = FormDockHideStaus.Hide; ! H& ^1 N$ f2 `3 n
                    return; $ ~7 V. J, o" m! C* S7 i6 A
                } 8 w  o( H! P! |3 ^& Z, ?
                else
# s) q+ Y, |% }) ~+ A0 j8 m                { 8 w; }5 x: c5 u3 R$ G6 e
                    if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width) + K  F$ ~" a0 d+ L$ Q( M
                    { ) ?+ R5 g( ?; c' `
                        this.dockHideType = DockHideType.None;
2 @1 J5 ~: k; W                        this.formDockHideStatus = FormDockHideStatus.ShowNormally;
8 V* O8 [6 `7 Z  L% _* p                        return; 6 z& l, y/ `) M& h
                    } % ?7 d/ z% y2 o, D
                    this.dockHideType = DockHideType.Right;
8 f) U4 V* t* T" J! K2 a, f: B                    if (this.dockedForm.Bounds.Contains(Cursor.Position))
8 B& ~+ k" ?2 X6 H. t$ F                    { - C. b* t! f" V
                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
8 H0 v$ Y& s0 D                        return; 4 X# w- Z; ]8 A+ R9 a9 X
                    } / r8 \3 p: t# P( t1 i/ }' k
                    this.formDockHideStatus = FormDockHideStatus.Hide; / ^/ Z+ j! o# m9 b
                    return;
8 C) ]( H: G3 R$ J. h                } : Z8 P1 y3 `6 J, }$ D  z
            } 1 j! d* t: F1 ]
        }  a9 l+ e* M/ k, S& I$ A" B
复制代码9 Y4 i% a: O1 ^. [

# j( ~; x2 Q+ w4 g
, r: v! j+ T4 Q7 O' x$ U9 b上面的代码主要体现了以下几个要点:
7 R* Q, e8 l5 T; D" H
$ X. v+ Q9 e; N5 C5 a- {9 b6 a: _(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。! m+ L8 H, A) E( X
(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。. Q3 q% g2 r/ a& T+ n
(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。* x7 W4 i7 j9 `/ F3 D. d
详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。$ r, x0 h2 M2 e: E% G
$ k* @7 B: w' @
3.定时检测满足/退出隐藏条件
3 P3 F; Q" S+ [9 x( B( }
2 O+ z! d3 p$ y我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。
- W' e6 y- `; I  t, L6 r$ R" X/// <summary>
* p! K  S, d/ G$ n+ P( V        /// 定时器循环判断。         
3 {3 G1 f' v5 r: M5 L* F        /// </summary>        
3 g$ q" s% K) m        private void CheckPosTimer_Tick(object sender, EventArgs e) . c8 z1 U. q+ L' [% e; |
        {//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外) ! W' {, b$ V5 O
            if (this.dockedForm.Bounds.Contains(Cursor.Position)) ( _! e: @4 x5 g5 c6 J
            {                             if (this.dockHideType!= DockHideType.Top)
/ N( }7 [% q9 X# R9 w                { # y4 ?' o0 ~) `4 Y: C( Y
                    if (this.dockHideType!= DockHideType.Left) 9 A* b+ z4 M6 F# ^: S% Z) g
                    {
+ n! ?+ z% S! J                        if (this.dockHideType!= DockHideType.Right) . k! R& j- P- D# i( e& _, p8 D
                        { - z" l) L. p2 M* U( C
                            return; 3 X# ?& j4 E3 Z: V* N/ r, v8 L
                        }
. o: e, }( E8 }# n                        if (this.formDockHideStatus == FormDockHideStatus.Hide) 1 l6 N  B& s. n' H
                        { 4 Y6 E" p0 ]" T2 ~/ v( w: m0 P: s
                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y); # R& U2 _! J2 S
                            return;
2 H$ c6 K' Y' }! P  u8 \                        } / g5 }+ G- @& f5 o- j* A( k
                    }
, M" m4 d$ o/ I, z& D) ^                    else 1 {  q1 p. v1 r& ?  `( e) O3 T
                    { 4 F" i2 t' }0 h1 E: [2 f
                        if (this.formDockHideStatus == FormDockHideStatus.Hide)
: Z: \3 d1 Z4 E) z4 R5 t                        {
' B; Z, a/ Z, T8 e. K                            this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y); ; e% c) m2 c: T( ^, U# p& @: ]& ]5 v
                            return;
% f0 H+ F+ w8 f: X' G                        }
/ f" \$ g+ X& g; l3 r; X! X                    } 8 T: _) p* U- S
                } - N. b! X1 g( ?
                else " [, f/ A) i8 J6 ?
                {
4 I/ T8 g/ T, v& z6 P# T                    if (this.formDockHideStatus == FormDockHideStatus.Hide)
/ S  N( Q: A* X/ _3 p1 Z* G: X: m                    { / f) ~; M1 ], ?; L
                        this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0);
6 N  B* U3 J. j: f  `" ]                        return; + A( W2 g! Z4 j- p! l" J. `4 b
                    }
0 H) L) u% R0 p2 [7 Z                } + M6 b2 y9 I7 y3 x4 w  D% A
            }
; h. l1 R* C9 W5 q5 z8 {6 O            else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。 & m6 K* k7 \7 X1 L
            {                switch (this.dockHideType) ) F  t5 }" q7 P8 I6 l* ]
                {
2 j) Z9 T3 Y; S6 i                    case DockHideType.None:
) K2 D! N; \" B                        {
; P2 v# Y, J/ ~( S                            if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally &&  
, v- m, q) b7 u/ \( |                               (this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height))
7 u8 P7 {2 r0 o* }+ e, c                            {
6 U& s; s  B# q; E. W# A                                this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height); % e# D' l7 j: ~" n; R
                            }
8 U- p4 E6 ?! A, ?/ V+ L                            break; 6 M7 L. r4 y0 C! E9 }, S( P
                        }
, }. Q& ^- T3 k; U                    case DockHideType.Top:
! j5 o1 M5 _1 F' T1 t8 x                        {
' u5 X# G" e$ x4 [                            this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1);
3 X  J* b5 ^9 q' j- }                            return; 3 U' ?# W, |! z% w) P2 {1 \' f
                        }       % ^9 d* T/ i  z6 J
                    case DockHideType.Left:
, K: ?% Y5 O+ f  O9 a! B9 x5 _: r                        {                            ( T; K( `) K% D9 B
                            this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y);
% N, q, b6 P" n8 L  R  S                            return;
; }# J' m5 Q- c( \# [9 _                        }
7 z' p  n. E7 S2 Y                    default:
) n7 D0 f5 u. F7 C: J                        { ) l5 ]  c4 U' E# m6 r1 C
                            if (anchorStyles2 != DockHideType.Right)
# j  b% f* i& C# O  P) H9 V) ?+ E                            { 8 K  e6 b9 |; B. V/ T% \
                                return;
, S4 A+ P% k/ s! f1 n                            }                             ' O/ \; W4 X0 b+ H0 c/ V' U7 `
                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y); - `" I$ f3 A7 h9 ]. }, f% H
                            return;
3 M  Q8 p( d) k& ]                        } / [! Z- P& \1 T8 e9 ^. y* M! E: u
                } 5 H1 z4 H7 e5 W4 K+ F0 q7 d
            } % J# o) Q; k! y, ]
        }
! ?8 ?; \: W& e+ t复制代码# J3 j. S* \( Y- k2 J# i
7 v- {4 y: O$ O0 r$ k* o
6 d, p. w+ R* \8 V5 t% I' G
(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。
3 m: ~$ x! `& g( d0 O, x(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。
+ f; R4 h9 Q+ r3 d7 ~& I(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。0 _( ]1 H- W, S3 b2 D3 r  g7 S
# g, Q. _& N' O. s/ }' m
三.如何使用AutoDocker组件# v7 T( w- k7 x/ [* D4 K

; F+ d: Q2 I7 L- _# x7 R5 c1 hAutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:
, V/ H  H& Y3 h" W/ i- n& Y从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:
6 y. D) ~. A# h0 ?1 }3 z9 B. Wthis.autoDocker1.Initialize(this);: e" [" t! u+ D9 I
复制代码. |' q0 w: C& D
这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~5 W5 P; J3 s5 o3 @
& L0 `7 P; o; N. R$ \8 R6 b) n' [
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

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

GMT+8, 2025-12-14 20:05 , Processed in 0.080229 second(s), 19 queries .

Powered by abc369 X3.4

© 2001-2023 abc369.

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