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 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;
            }
        }

        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
        }

        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);
            }
        }
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM