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