★我要吧★

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

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

[复制链接]
发表于 2016-2-14 09:08:05 | 显示全部楼层 |阅读模式
QQ有个靠边隐藏的功能,使用起来很方便:在屏幕上拖动QQ的主窗体,当窗体的上边沿与屏幕的上边沿对齐时,主窗体就会duang~~地隐藏起来,当将鼠标移到屏幕上边沿的对应区域时,主窗体又会duang~~显示出来。
& n( f. c, \7 e" w( k7 L* |0 W3 `6 ?- `% [1 y
6 k4 e2 ~% n* I1 M- I

1 c1 _9 g; ^- t我在GG的最新版4.2中也增加了靠边隐藏的功能,支持靠左边沿隐藏、靠上边沿隐藏、靠右边沿隐藏三种模式,并且,将靠边隐藏实现为了一个可复用的组件AutoDocker。' b4 B9 p/ f. U$ E) P
那么,靠边隐藏功能到底是怎么实现的了?  e' r. j0 F+ l" K" O

& k' `9 O3 R1 t1 U5 Q一.靠边隐藏的原理
0 T% ~, G$ {5 V$ H3 U
6 }: P+ x$ f* Y+ Z5 S靠边隐藏的本质实际上并不是将窗体的Visiable设为false,而是将整个窗体的位置挪到屏幕区域之外。比如,靠右边沿隐藏,实际的效果图如下所示:/ f; D' y7 K, G
方案说明如下:
& M7 a& i" M* L# Y; \2 Q8 h(1)当拖动窗体在屏幕上移动时,检测窗体的位置,是否抵达了屏幕的边界,如果达到了边界,则准备靠边隐藏。; V- @6 j  M4 y9 P
(2)当达到了隐藏条件,并且鼠标的光标已经离开了主窗体,则实现隐藏。
6 c8 S; ?: {3 [% t(3)窗体隐藏后,当鼠标的光标移动到窗体与屏幕相交的边界位置时,则正常显示窗体;之后:
  p: \% [( e' B, `* H' P7 Z" C- a2 O     a. 当鼠标再度离开窗体区域,则又隐藏窗体。
4 B* L2 K8 K/ {4 x; }) ]     b.如果鼠标拖动窗体改变了其位置,使其不再满足隐藏的条件,则之后一直正常显示窗体。; w( ?' z  L5 ^- C2 z9 a6 X# o: ^
+ d% X2 u* Y* e* w
二.具体实现过程
( V/ S. m" @" u9 W3 P5 Y, G0 }/ ?) k8 u- h
1.基本元素定义; U. ?+ f  y! R. i- k) h
首先,我们需要定义靠边隐藏的类型:靠左、靠上、靠右。使用DockHideType枚举表示:7 R$ w- A; f- o3 N+ B( M! p' |, u
/// <summary> + ]/ {5 A. q0 ]7 k& t
/// 靠边隐藏的类型。 5 |0 p. s0 Z9 o' j6 Z7 C3 n2 `! J9 P
/// </summary>
& A+ @! y: j# I( Q9 b/ b( \% a* kpublic enum DockHideType # D3 G  \+ B  e: ^2 C
{ " v4 Z6 k; T- O/ A+ `
     /// <summary>
& M4 c( P. @, ]8 d9 l; c" a. C* ~     /// 不隐藏 & E- y4 F$ X$ v2 Q" i7 s( r
     /// </summary>
' S0 [  M8 l2 l     None = 0,
  w$ g& e& ~3 T' m. ?& O; ~+ X     /// <summary> . k% X: ~1 I/ S1 R: [- q
     /// 靠上边沿隐藏
$ S5 E2 Y$ j0 @3 ]     /// </summary> 7 t2 \% W; t% @
     Top, + N1 r, R! {" `& b
     /// <summary> , \' k2 z& c. E% _$ ?
     /// 靠左边沿隐藏 ( u$ a/ P, {: f; s
     /// </summary> 7 Q) P6 w  x9 T: l  N
     Left,
) O% [  B! o6 v% E- ]# w2 J     /// <summary>
0 A; a; d: `% j     /// 靠右边沿隐藏
( @5 ^; T: ?: @8 q     /// </summary> $ |+ \; }% r, [0 Z
     Right * n+ L3 B+ H  u8 [
}2 l' X# N7 u5 q- s, ^
复制代码* U+ G5 ]0 J) O: i' g

  w! j2 N2 Z5 _% Y其次,根据上面的原理描述,我们知道窗体有三种状态:正常显示、准备隐藏、已经隐藏。这三种状态使用FormDockHideStatus枚举表示:
" Y; B" n: H6 c5 c/ I/// <summary> ( U7 k4 F! |0 c6 a/ i/ B
    /// 窗体的显示或隐藏状态
0 p& @( ]6 F6 ^- \    /// </summary>
; X: [( i/ S8 c' t& j    public enum FormDockHideStatus 7 r; N9 V+ N# t6 p7 |& v
    {
5 u0 G+ _! f- [  o        /// <summary>
2 \7 K" M# ?% P9 ?8 P% \        /// 已隐藏 0 |  y2 _5 N. [0 F) p1 \/ |4 i$ n; Z
        /// </summary> # m) p. L' C: w4 I" A
        Hide = 0,
+ k4 @7 l9 b& H* V9 o) E9 g. {& I$ g8 `) ^4 {( z. F" Q
        /// <summary> $ f& u3 R3 U* d. m
        /// 准备隐藏
" z" ~6 u) X* u* |; [6 {% P: z3 i        /// </summary> 3 V4 H; T; S) X8 n( B, Y
        ReadyToHide, ) K5 h& P+ x/ t1 |( ?

# V$ F5 D0 W# m2 `( _        /// <summary>
! v3 N1 Z! [8 z. F( Y1 v2 r8 |        /// 正常显示
: p% N7 J$ B7 T0 N        /// </summary>
2 h( o4 B9 Z, t        ShowNormally 5 Z: o; @* c# a0 L! W
    }
2 E' S2 t, k$ v* s% _. _复制代码
3 j$ @/ J( J: O" C7 |0 Z0 m: [$ d. L( n3 [2 K8 W# V

- e7 g! D+ M9 Y0 A& S2.判断是否达到隐藏条件
/ q8 N' ^/ p# Y8 v" M8 c  r. K) P9 e; o. {
很明显,我们应当在每次窗体的位置发生变化时,做出这样的判断,所以,这个判断应该在Form的LocationChanged事件中调用。& |3 \8 Q* s" |( E$ s
private void dockedForm_LocationChanged(object sender, EventArgs e)
9 v" M- g) f, j+ {3 V0 k" l. n        {
/ t/ q/ X- }1 a/ d. ~            this.ComputeDockHideType();
4 t3 |9 ]) H6 h7 ^$ f4 ?' e            if (!this.IsOrg) , T" i; T3 w) S% z+ E. |7 t
            { ; S7 r3 C6 c) J8 z( e9 v
                this.lastBoard = this.dockedForm.Bounds; 7 S5 m& a4 J& Y9 M) ~3 D9 P9 @6 T
                this.IsOrg = true; : [& {0 H* b( ^* v+ _2 o
            }
# d, d+ r7 L, W" f0 p$ g        } ( d& Q, l# J; v: t

: x; B$ N' f, V7 G        /// <summary>   i4 c, L; Y1 E! i& f" K8 V
        /// 判断是否达到了隐藏的条件?以及是哪种类型的隐藏。
1 \% u( a# J1 e! l$ j8 t* [        /// </summary>
) S  z& y, J. ~% m8 i        private void ComputeDockHideType() ; l6 Q1 F, L; K. @
        {
2 Q0 F1 i- e1 Y1 h% K            if (this.dockedForm.Top <= 0) . V- D1 n' W0 S* o2 t
            { ( ^: I+ p. Y* ?" k  \
                this.dockHideType = DockHideType.Top; ! e4 _7 u9 W- `; t# j+ h
                if (this.dockedForm.Bounds.Contains(Cursor.Position)) " K  R' ~$ \* X9 D* ~5 N
                {
* ]. N3 }8 k# _6 s8 O; U% p/ t                    this.formDockHideStatus = FormDockHideStatus.ReadyToHide; % S: O3 n- p5 w; d9 a% }" G
                    return;
8 |/ R$ F3 U. d' \5 |4 {. S                } , a4 E2 E5 Y( Z! _
                this.formDockHideStatus = FormDockHideStatus.Hide;
2 j+ k; W! Y: L  h3 Y! W                return;
4 C8 ]& p6 F$ z0 X4 }$ {$ s            } ' t3 `2 N( p; z* D+ c. o
            else ' A. d4 t' I9 c# S. R  V
            {   L9 }' s8 x- n
                if (this.dockedForm.Left <= 0)
! N" S7 c( l) y# W" R$ h' H                {
' f- X  E# |& A( z7 W9 C) Z, L                    this.dockHideType = DockHideType.Left;
3 w2 [$ O7 n8 q7 _! i4 f                    if (this.dockedForm.Bounds.Contains(Cursor.Position))
' I- [8 Y' s' m) p) \" f; Q# t: c8 ~' Z                    {
. t8 v/ {4 Q3 f4 w8 f# W" I; W                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide; # B: U( n. u0 a
                        return;
; V; E* Z* E0 J  e* ~# m                    } ; c0 m8 F* n/ t, K" ~9 l4 Y9 t8 }
                    this.formDockHideStatus = FormDockHideStaus.Hide;
/ E$ W$ z4 {7 _" g                    return;
* B, G. D! y+ n0 l- C                }
0 z# L" n$ H- c% Q! t5 m8 K9 C8 Y                else 4 Y: `+ z) K$ T  U
                {
7 q" t3 c( W: k/ w0 h- x                    if (this.dockedForm.Left < Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width) ; t  \, Q" T* }& L
                    {
. W1 T) u- z/ k8 v8 b                        this.dockHideType = DockHideType.None;
, s4 l! k; m1 Y6 h3 [8 Z                        this.formDockHideStatus = FormDockHideStatus.ShowNormally; 3 M; Z- M$ Y+ s2 F7 M/ ]' Q
                        return; 5 e& l5 ?$ V5 o/ ?0 a; e
                    }
9 Y8 K' ^% J1 f8 C+ y6 L                    this.dockHideType = DockHideType.Right; 3 k6 f3 B# M! \; Y" l9 I" \/ l. U
                    if (this.dockedForm.Bounds.Contains(Cursor.Position)) 3 P: D7 v8 T, @
                    {
" v% q! L- a$ V. Y) m4 D: _                        this.formDockHideStatus = FormDockHideStatus.ReadyToHide;
2 n( h; V9 q; e3 {                        return;
  c! ^( _6 ]6 f# K8 Y% r                    }
$ x' v; z+ S. x# s9 j) h9 Y                    this.formDockHideStatus = FormDockHideStatus.Hide; " m% Q1 J+ ~' |5 b" F
                    return;
% W. @; c2 [1 ?, O                } ! ?8 g' G; v" |! L
            }
7 ?  l" I1 B( B+ p' y7 x7 z, U        }
1 k/ C" p9 l. K9 p复制代码
' ^, `% W3 n5 ]! Q1 R. o+ _& C# y+ D) x8 @, d) _4 u! e

! h" X. d6 j$ q! U上面的代码主要体现了以下几个要点:% |6 |) \1 e2 p; ]
# P8 n* C4 [+ M7 W; ?
(1)靠边的优先级判断:最先判断靠上边沿隐藏、其次判断靠左边沿隐藏、最后判断靠右边沿隐藏。
, {  N4 Q& ~' Z(2)只要窗体的某一边超出的屏幕的边界(比边沿对齐更加容易控制),则视为达到隐藏条件。
, p) H) t$ s& M9 E$ T2 a* y$ F( A(3)如果达到了隐藏条件,仍然要判断光标的位置(Cursor.Position)是否在窗体内,如果在其内,则为准备隐藏状态。
2 ^8 y% b0 U5 g. b& l$ ^$ ]8 P详细分析一下上面的过程,就会发现,当处于准备隐藏状态时,如果将鼠标移出到窗体外(这次移动并没有拖动窗体改变其位置),那么,窗体会一直处于“准备隐藏”的状态。所以,此时,必须要有一个机制来触发它,真正进行隐藏动作。我是用一个定时器来循环判断的。8 Y* p8 W/ E& u' l( k, Y+ y- a
+ l. h% G# j& w6 f0 \6 p
3.定时检测满足/退出隐藏条件
4 A+ Y. d7 G+ n7 u0 O: T4 q
  R, e4 n' P# C: I# w( Q我使用一个定时器,每隔300ms检测一次,用于判断从正常显示到隐藏、以及从隐藏到正常显示的转变。
) I' o3 `5 }  H8 z2 t/// <summary> 3 ]& a( a* J- t( s2 N5 a5 t
        /// 定时器循环判断。         
4 W* r: O# a5 {# i9 P        /// </summary>        & N) T  x' e+ A$ {1 p" f
        private void CheckPosTimer_Tick(object sender, EventArgs e)
+ l3 b4 d7 Q. G( n1 F; o" W4 f# y        {//当鼠标移动到窗体的范围内(此时,窗体的位置位于屏幕之外)
; M/ l! C- B1 D/ C' D  @            if (this.dockedForm.Bounds.Contains(Cursor.Position)) ; |4 H2 E$ D4 G0 ]9 p6 J
            {                             if (this.dockHideType!= DockHideType.Top) # [9 F7 J. m$ m) ^3 I. Q2 W
                { 6 k- U& ^$ n' x5 Y$ i3 m
                    if (this.dockHideType!= DockHideType.Left) . g6 l4 N1 |, m7 u
                    {
- c# @4 ~% j# O. e/ C* u% y6 S" \                        if (this.dockHideType!= DockHideType.Right)
+ E2 ~# _- z# |4 H* @                        {
+ ?$ Z/ ^! r9 u# M5 E% F* E: ~4 i' c                            return;
. d1 _4 h& ~8 o3 M% `2 z! x) r: u                        }
' f4 G- P9 K9 z2 x) ^" j' z                        if (this.formDockHideStatus == FormDockHideStatus.Hide) & s5 }1 `7 a+ r2 M! M4 t& d
                        { & ^# R% }2 F+ N: e" |: n+ M6 G4 q
                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - this.dockedForm.Width, this.dockedForm.Location.Y); 8 J9 \, S9 M" S# V$ D4 r: {1 U1 R
                            return; + m# ]/ H1 y. ^, u) X
                        } 0 z6 B9 |0 A2 L0 I& C
                    } ) n; k- h) o" H1 X# M  W$ g
                    else
# Z. `, ~8 j; n# k4 a% J                    { : t6 Q; \# J$ n  i; R' v- h
                        if (this.formDockHideStatus == FormDockHideStatus.Hide) ' o0 }! p. [1 c+ G3 }
                        {
: D9 H" W& ?- k/ v                            this.dockedForm.Location = new Point(0, this.dockedForm.Location.Y);
$ v8 ^7 R! H' r0 w# E( i' J                            return;
7 Q0 h: I5 N7 {& ~4 W3 }# z                        } / l0 m0 r$ z' s  G  V. {2 Q5 o: H
                    }
) V: t0 }" S/ ^. B3 A                } 4 S7 @6 ]: N- j& r: ~# L
                else / ^3 K: N: B" Z8 T5 s
                { " c, {: x! W$ M, h
                    if (this.formDockHideStatus == FormDockHideStatus.Hide)
7 `. T+ K# Z) B3 S, m1 k" y                    {
( o/ S5 A1 I" Q/ u3 z0 I; X+ k                        this.dockedForm.Location = new Point(this.dockedForm.Location.X, 0); % S8 j8 P) E6 U6 s/ a
                        return;
/ K- T& X/ E  U/ r3 `                    } 0 I9 @$ W5 o* ?* c: W1 V$ r
                } # w6 J7 ]2 b$ u1 u
            }
) @9 g# Q* w9 H' m7 m# S: @8 j            else //当鼠标位于窗体范围之外,则根据DockHideType的值,决定窗体的位置。 - k2 _8 i" u; f0 j6 s! `
            {                switch (this.dockHideType)
6 G( T. }* L! b4 f2 A1 |                {   u1 Y7 t2 _' e
                    case DockHideType.None: 0 L, _. C# h0 A- L5 _+ c1 ^
                        {
0 A7 V4 s0 y4 ?% W5 {; r) {                            if (this.IsOrg && this.formDockHideStatus == FormDockHideStatus.ShowNormally &&  ' t: F  [. }! m2 f* t9 f
                               (this.dockedForm.Bounds.Width != this.lastBoard.Width || this.dockedForm.Bounds.Height != this.lastBoard.Height))
% Q4 `% w( R6 S/ ~- K                            { . ?  O- @! I0 o3 L7 u7 f1 o
                                this.dockedForm.Size = new Size(this.lastBoard.Width, this.lastBoard.Height); * q5 D: n' c, G' [& J! ]$ V& |0 w
                            }
/ I/ d5 D0 H% Y" e  [5 e" s" d                            break;
& x) A) K) i, q4 S, t4 n& B6 {6 H! j                        } 5 i' S" o: K4 ^  Y' y
                    case DockHideType.Top: + }& o+ N8 U  m8 p& h9 f
                        { , z& q7 {5 T/ C4 h2 Z) E3 o) ]
                            this.dockedForm.Location = new Point(this.dockedForm.Location.X, (this.dockedForm.Height - 4) * -1);
" l8 k* l5 ]- A8 l: d& E& W                            return;
4 f6 ^/ _1 M! M9 u# S+ m6 X                        }       % u- Q2 j2 @5 w& p# w* V1 ?! P
                    case DockHideType.Left:
: p+ [6 a, A$ v0 H" Q9 G+ o7 B                        {                            8 w; j# K  E; H4 c
                            this.dockedForm.Location = new Point(-1 * (this.dockedForm.Width - 4), this.dockedForm.Location.Y);
8 M5 c9 v/ m! U# Y+ H' ~+ z$ K# c                            return;
4 V" d0 B" e% v7 \  E/ p, f6 [. P- c. o                        }
4 Y( t, y+ p  M* E7 ?; X                    default:
% ]7 f7 _2 y3 A8 O/ {                        {
1 c& f7 {" @& L7 E& U7 ~" u                            if (anchorStyles2 != DockHideType.Right) 8 x0 p: |- w- Y4 Q+ a, Z( I: [3 H
                            { % E9 a, ]: t; p0 ~& j. n
                                return;
1 m- ^, E$ E( i7 b/ A; A                            }                             
2 {6 n& ~2 M6 J: l% t                            this.dockedForm.Location = new Point(Screen.PrimaryScreen.Bounds.Width - 4, this.dockedForm.Location.Y);
- L) i; ~8 T" x, Q/ a. U                            return;
3 s" ^5 H: O# [: u                        }
3 H2 L6 x! Q( c# B! G                }
0 p9 p& R$ k& a- N9 A4 O: R            } / A0 N% f4 P4 ?
        }* B) K* T: d: X* b& K9 v: P# c% C
复制代码
5 W! \# v. Y/ _, b& d7 [" B  x1 x* ]
2 }0 y+ x, |0 N  Q+ {% y
  P0 }7 C, w2 i  M(1)在窗体隐藏的情况下,准确地说,是窗体在屏幕区域之外时,将鼠标光标移动到窗体上(实际上,是窗体的边界),则修改窗体的Location,让其正常显示。
; ?: l  ^$ b1 V* O(2)从(1)描述的窗体隐藏切换到正常显示时,代码对窗体的位置进行了控制,使其的边界恰好与屏幕的边界对齐,这样做的目的是,当鼠标再离开窗体范围时,窗体又可以duang隐藏起来。
  I, n. @9 Y! {( C1 @  K(3)定时器的循环检测配合鼠标拖动窗体的事件处理,就完全实现了类似QQ的靠边隐藏的效果,而且,我们比QQ更强,QQ只实现了靠上隐藏。
: X' L1 ^8 w. _1 ]
3 v+ v. x# x3 n. Z三.如何使用AutoDocker组件( b9 \9 ?- m- u- A' l

2 o! v7 G) z, [, q" H' kAutoDocker是以组件(Component)的形式实现的,编译后,会在工具箱中出现一个AutoDocker组件。其使用非常简单:
1 K& o0 j, ]# Y5 g. a. u9 o从工具箱中将AutoDocker拖放到主窗体MainForm上,然后在主窗体的构造函数中添加一行代码:
# S% u" R/ m( ]6 R! t) ^! Ethis.autoDocker1.Initialize(this);
5 _1 l4 ]# L( t$ [  T复制代码4 \- Q9 a8 {' _$ m( i& \  m1 p
这样,主窗体运行起来后,就拥有了自动靠边隐藏的功能了,是不是很duang~~~7 Z. b; R9 ^! e" t9 @- a6 D
- n, O% s: h$ ]0 L5 N
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

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

GMT+8, 2025-12-17 20:23 , Processed in 0.067340 second(s), 19 queries .

Powered by abc369 X3.4

© 2001-2023 abc369.

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