1. 程式人生 > >WinForm實現類似QQ停靠,顯示隱藏過程新增特效效果

WinForm實現類似QQ停靠,顯示隱藏過程新增特效效果

這可能是個老題長談的問題了,只是在專案中會用到這個效果,所以今天做個記錄。大家見了別噴我。在專案中的需求是這樣的。

開啟程式,在螢幕的右下角會顯示一個窗體,一般情況下該窗體會隱藏停靠在右邊,只露出很小部分,當滑鼠移動到這個很小部分時,窗體全部顯示,顯示過程是從右邊滑動到左邊,當滑鼠離開窗體時,窗體需要隱藏在右邊,只露出很小部分,隱藏過程是從左邊滑動到右邊。

實現此類效果我碰到的連個難點是:1、如何判斷滑鼠離開了窗體?2、窗體顯示隱藏過程中效果如何表現平滑(就是給人一種流暢感覺)?

1、判斷滑鼠離開窗體我開始想的是在WndProc方法中來獲取滑鼠座標然後根據窗體的Location來判斷,可能是小弟愚笨,該方法沒有處理好,執行後介面卡住了。然後我只有用個計時器,每隔幾秒獲取一下滑鼠座標在根據窗體的Location來判斷。獲取座標有個API方法GetCursorPos,有資料表明此方法是最優效率的,那我們就用它好了。

2、顯示隱藏效果開始想的也是改變窗體座標來實現,但是這個方法做出來的效果比較差,畫面感覺不流暢,後來查到可以用API方法AnimateWindow來實現這個效果,因此我們來認識一下AnimateWindow()方法;

此方法需要傳遞三個引數:

第一個引數:傳入需要顯示特效的窗體的控制代碼。

第二個引數:完成特效所花時間,單位:毫秒,也就是說你可以指定多少時間內完成指定的特效

第三個引數:指定特效型別,此引數可以指定多個,多個之間用|隔開。這裡列舉了一般的9個特效。有這9個基本夠用了。

關於這個方法的詳細資料我就不一一列舉了,大家在網上搜搜,很多資料的。下面進入正題。

1、建一個winform專案,命名:DockFormsApplication,名字大家可以自定義的。

2、建完後項目中會有個預設建立好的窗體Form1,修改Form1的text屬性為:“仿QQ停靠,加特效”

3、新增API方法AnimateWindow()和該方法需要的一些特效引數, 特效引數命名是我自己隨便命名的,大家就不要深究了,至於為什麼要這麼命名,我自己也不知道,反正能用就行

注意:AnimateWindow()方法需要引用using System.Runtime.InteropServices;

複製程式碼
        /// <summary>
        /// //從左到右
        /// </summary>
        public const Int32 AW_HOR_LEFT_RIGHT = 0x00000001
; /// <summary> /// 從右到左 /// </summary> private const Int32 AW_HOR_RIGHT_LEFT = 0x00000002; /// <summary> /// 從上到下 /// </summary> private const Int32 AW_VER_UP_DOWN = 0x00000004; /// <summary> /// 從下到上 /// </summary> private const Int32 AW_VER_DOWN_UP = 0x00000008; /// <summary> /// 從中間到四周 /// </summary> private const Int32 AW_CENTER = 0x00000010; /// <summary> /// 隱藏視窗 /// </summary> private const Int32 AW_HIDE = 0x00010000; /// <summary> /// 顯示視窗 /// </summary> private const Int32 AW_ACTIVATE = 0x00020000; /// <summary> /// 使用滑動型別。預設則為滾動動畫型別。當使用AW_CENTER標誌時,這個標誌就被忽略 /// </summary> private const Int32 AW_SLIDE = 0x00040000; /// <summary> /// 改變透明度 /// </summary> private const Int32 AW_BLEND = 0x00080000; /// <summary> /// 特效花費時間 單位:毫秒 /// </summary> private int _speed = 500; [DllImport("user32.dll")] public static extern void AnimateWindow(IntPtr hwnd, int stime, int style);//顯示效果
複製程式碼

3、新增API方法GetCursorPos用於獲取滑鼠座標。此方法需要傳入一個座標物件。該物件是一個二維結構。儲存座標的X值和Y值。

複製程式碼
        /// <summary>
        /// 滑鼠座標
        /// </summary>
        private Point _cursorPoint;

        //API獲取滑鼠座標
        [DllImport("user32.dll")]
        public static extern bool GetCursorPos(out Point pt);
複製程式碼

4、設定窗體顯示在右下角,並且重寫WndProc方法禁止滑鼠拖動和雙擊標題欄最大化

複製程式碼
        private void Form1_Load(object sender, EventArgs e)
        {
            //設定窗體顯示位置 右下角
            int workY = Screen.PrimaryScreen.WorkingArea.Height - Height;
            int X = Screen.PrimaryScreen.Bounds.Width - Width;
            this.Location = new Point(X, workY);
        }

        //重寫WndProc方法,禁止拖動和雙擊標題欄最大化
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x231)
            {
                this.SuspendLayout();
            }
            else if (m.Msg == 0x232)
            {
                this.ResumeLayout();
            }
            else if (m.Msg == 0xA1 && m.WParam.ToInt32() == 2)//禁止拖動
            {
                return;
            }
            base.WndProc(ref m);
        }
複製程式碼

後來發現,更改窗體屬性:FormBorderStyle值為:FixedSingle也可以達到禁止拖動的效果

5、因為要每隔幾秒獲取滑鼠座標判斷滑鼠是否在窗體範圍內,因此需要一個計時器。考慮到效能神馬的,我比較喜歡使用System.Threading.Timer,下面就是計時器嗦必須的幾個變數

    注意:這裡我需要說明一下,由於AnimateWindow()方法控制窗體特效只能窗體顯示和隱藏兩種狀態,每個特效完成後窗體要麼隱藏,要麼顯示,如何使特效過後窗體一直顯示,我想了個折中辦法,只要你隱藏了,我就再把你顯示出來,因此在計時器中需要對窗體進行操作,如此則需要跨執行緒訪問窗體,因此就需要使用委託了,然後Invoke就可以了。

        //執行緒暫停時間 單位:毫秒
        private int _timespan = 1000;private System.Threading.Timer _timer;
        private delegate void LoadListDelegate();
        private LoadListDelegate _loaddelegate;

6、程式邏輯需要的幾個變數

複製程式碼
        /// <summary>
        /// 窗體是否顯示,true——顯示、false——隱藏
        /// </summary>
        private bool _isActive = true;

        /// <summary>
        /// 停靠在邊緣時,顯示窗體的寬度
        /// </summary>
        private const int _smallX = 5;
複製程式碼

7、新增兩個方法,顯示窗體和隱藏窗體的兩個方法

複製程式碼
        /// <summary>
        /// 隱藏窗體
        /// </summary>
        private void SetHide()
        {
            if (_isActive)
            {
                AnimateWindow(this.Handle, _speed, AW_HOR_LEFT_RIGHT | AW_SLIDE | AW_HIDE);

                int X = Screen.PrimaryScreen.Bounds.Width - _smallX;
                int Y = this.Location.Y;
                this.Location = new Point(X, Y);

                AnimateWindow(this.Handle, 10, AW_BLEND | AW_ACTIVATE);
                _isActive = false;
            }
        }

        /// <summary>
        /// 顯示窗體
        /// </summary>
        private void SetActivate()
        {
            if (!_isActive)
            {
                AnimateWindow(this.Handle, 10, AW_BLEND | AW_HIDE);

                int X = Screen.PrimaryScreen.Bounds.Width - Width;
                int Y = this.Location.Y;
                this.Location = new Point(X, Y);

                AnimateWindow(this.Handle, _speed, AW_HOR_RIGHT_LEFT | AW_SLIDE | AW_ACTIVATE);
                _isActive = true;
            }
        }
複製程式碼

8、新增方法,判斷滑鼠是否在窗體範圍內,如果在範圍內,則顯示窗體,如果不在範圍內,則停靠在右邊並隱藏窗體

複製程式碼
        private void LoadControl()
        {
            #region 控制窗體顯示和隱藏
            //獲取當前滑鼠座標
            GetCursorPos(out _cursorPoint);
            //根據 窗體當前狀態,判斷窗體接下來是顯示還是隱藏。
            if (_isActive)
            {
                //當前窗體為顯示,則接下來是隱藏
                //如果滑鼠座標不在窗體範圍內,則設定窗體隱藏,否則不處理
                if (_cursorPoint.X < this.Location.X || _cursorPoint.Y < this.Location.Y)
                {
                    SetHide();
                }
            }
            else
            {
                //當前窗體為隱藏,則接下來是顯示
                //如果滑鼠座標在窗體範圍內,則設定窗體顯示,否則不處理
                if (_cursorPoint.X >= this.Location.X && _cursorPoint.Y >= this.Location.Y)
                {
                    SetActivate();
                }
            }
            #endregion
        }
複製程式碼

9、新增計時器,每隔1秒判斷當前滑鼠位置,因為用到委託,因此需要在構造方法中新增一行程式碼_loaddelegate = LoadControl;用於指定委託的方法:

複製程式碼
        private void Form1_Load(object sender, EventArgs e)
        {
            //設定窗體顯示位置 右下角
            int workY = Screen.PrimaryScreen.WorkingArea.Height - Height;
            int X = Screen.PrimaryScreen.Bounds.Width - Width;
            this.Location = new Point(X, workY);

            //窗體開啟的時候就開始計時器
            BeginTimer();
        }

        private void BeginTimer()
        {
            TimerCallback tcBack = new TimerCallback(InvokTimer);
            _timer = new System.Threading.Timer(tcBack, null, 5000, _timespan);
        }

        private void InvokTimer(object state)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(_loaddelegate);
            }
        }
複製程式碼

10、為了窗體一開啟和關閉時有特效顯示,需要重寫OnLoad方法和實現Form1_Closing方法

複製程式碼
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            //從右到左滑動
            AnimateWindow(this.Handle, _speed, AW_HOR_RIGHT_LEFT | AW_SLIDE | AW_ACTIVATE);
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            //淡出效果
            AnimateWindow(this.Handle, 1000, AW_BLEND | AW_HIDE);
        }
複製程式碼

到此,所有程式碼編寫完成,如果想要有更好的體驗,可以設定一下窗體的以下屬性值:

            this.MaximizeBox = false;//取消最大化按鈕
            this.MinimizeBox = false;//取消最小化按鈕
            this.ShowInTaskbar = false;//工作列不顯示窗體圖示
            this.TopMost = false;//設定窗體總是顯示在最前面


完整程式碼如下:

複製程式碼
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;

namespace DockFormsApplication
{
    public partial class Form1 : Form
    {
        #region 屬性 API特效窗體顯示和隱藏

        /// <summary>
        /// //從左到右
        /// </summary>
        public const Int32 AW_HOR_LEFT_RIGHT = 0x00000001;
        /// <summary>
        /// 從右到左
        /// </summary>
        private const Int32 AW_HOR_RIGHT_LEFT = 0x00000002;
        /// <summary>
        /// 從上到下
        /// </summary>
        private const Int32 AW_VER_UP_DOWN = 0x00000004;
        /// <summary>
        /// 從下到上
        /// </summary>
        private const Int32 AW_VER_DOWN_UP = 0x00000008;
        /// <summary>
        /// 從中間到四周
        /// </summary>
        private const Int32 AW_CENTER = 0x00000010;
        /// <summary>
        /// 隱藏視窗
        /// </summary>
        private const Int32 AW_HIDE = 0x00010000;
        /// <summary>
        /// 顯示視窗
        /// </summary>
        private const Int32 AW_ACTIVATE = 0x00020000;
        /// <summary>
        /// 使用滑動型別。預設則為滾動動畫型別。當使用AW_CENTER標誌時,這個標誌就被忽略
        /// </summary>
        private const Int32 AW_SLIDE = 0x00040000;
        /// <summary>
        /// 改變透明度
        /// </summary>
        private const Int32 AW_BLEND = 0x00080000;

        /// <summary>
        /// 特效花費時間 單位:毫秒
        /// </summary>
        private int _speed = 500;

        [DllImport("user32.dll")]
        public static extern void AnimateWindow(IntPtr hwnd, int stime, int style);//顯示效果

        /// <summary>
        /// 滑鼠座標
        /// </summary>
        private Point _cursorPoint;

        //API獲取滑鼠座標
        [DllImport("user32.dll")]
        public static extern bool GetCursorPos(out Point pt);

        //執行緒暫停時間 單位:毫秒
        private int _timespan = 1000;
        private System.Threading.Timer _timer;
        private delegate void LoadListDelegate();
        private LoadListDelegate _loaddelegate;

        /// <summary>
        /// 窗體是否顯示,true——顯示、false——隱藏
        /// </summary>
        private bool _isActive = true;

        /// <summary>
        /// 停靠在邊緣時,顯示窗體的寬度
        /// </summary>
        private const int _smallX = 5;

        #endregion

        public Form1()
        {
            InitializeComponent();
            this.MaximizeBox = false;//取消最大化按鈕
            this.MinimizeBox = false;//取消最小化按鈕
            this.ShowInTaskbar = false;//工作列不顯示窗體圖示
            this.TopMost = false;//設定窗體總是顯示在最前面

            _loaddelegate = LoadControl;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //設定窗體顯示位置 右下角
            int workY = Screen.PrimaryScreen.WorkingArea.Height - Height;
            int X = Screen.PrimaryScreen.Bounds.Width - Width;
            this.Location = new Point(X, workY);

            //窗體開啟的時候就開始計時器
            BeginTimer();
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            //從右到左滑動
            AnimateWindow(this.Handle, _speed, AW_HOR_RIGHT_LEFT | AW_SLIDE | AW_ACTIVATE);
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            _timer.Dispose();
            //淡出效果
            AnimateWindow(this.Handle, 1000, AW_BLEND | AW_HIDE);
        }

        //重寫WndProc方法,禁止拖動和雙擊標題欄最大化
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x231)
            {
                this.SuspendLayout();
            }
            else if (m.Msg == 0x232)
            {
                this.ResumeLayout();
            }
            else if (m.Msg == 0xA1 && m.WParam.ToInt32() == 2)//禁止拖動
            {
                return;
            }
            base.WndProc(ref m);
        }

        /// <summary>
        /// 隱藏窗體
        /// </summary>
        private void SetHide()
        {
            if (_isActive)
            {
                AnimateWindow(this.Handle, _speed, AW_HOR_LEFT_RIGHT | AW_SLIDE | AW_HIDE);

                int X = Screen.PrimaryScreen.Bounds.Width - _smallX;
                int Y = this.Location.Y;
                this.Location = new Point(X, Y);

                AnimateWindow(this.Handle, 10, AW_BLEND | AW_ACTIVATE);
                _isActive = false;
            }
        }

        /// <summary>
        /// 顯示窗體
        /// </summary>
        private void