C#窗口移動特效,Download()

在C#自定義皮膚中,無邊框移動特效非常需要。

第一步:

       //先定義一個座標點對象 mypoint
       
private Point mypoint;

第二步:在窗體的點擊事件中寫

       private void Form1_MouseDown(object sender, MouseEventArgs e)
        {

            //
鼠標在窗體內按下時,自動記錄鼠標的 x y 值,並將它們改爲負數
            mypoint = new Point(-e.X,-e.Y);
       }

第三步:在窗體的鼠標移動事件中寫

private void Form1_MouseMove(objectsender, MouseEventArgs e)
        {

            /*
               
鼠標在窗體內移動時,首先判斷e.Button按下的是哪個鼠標按鈕再判斷
             * 是否等於左鍵按下,MouseButtons.Left這句的意思是鼠標左鍵按下狀態
             */
           if (e.Button == MouseButtons.Left)
            {

               /*
新建一個座標點對象,它的座標等於(Control.MousePosition;
                * 桌面上座標的位置
                */
                Point myposition = Control.MousePosition;
               /*
                   myposition.offset
中的Offset是座標平移的意思,現在將在窗體
                * 內點擊左鍵時產生的負數加進來,也就等於 現在鼠標在桌面上的座標
                * 減去鼠標在窗體內的座標位置,就等於現在窗體的位置
                */
               myposition.Offset(mypoint.X,mypoint.Y);
               /*
                this.DesktopLocation
這句的意思是獲取或設置窗體在桌面上的位置
                *
它的位置來自myposition
                */
                this.DesktopLocation = myposition;
         
   }

 

其實這是個讓人說過無數次的內容,但是最近在寫一個測試小程序的時候發現了一個問題,今天沒什麼事做,就做個小的總結。

通過拖動窗體的客戶區來移動一個窗體並不是很新鮮的內容,很多的程序都用到了這一點,尤其是一些可以換膚的程序。

這篇文章並不打算詳細論述如何在C#下實現這一功能,因爲它的代碼實在是簡單得不能再簡單。這裏簡單說一下實現的原理:

首先說一個概念——窗體的客戶區,窗體的客戶區指的是一個窗體除了標題欄和邊框以外的部分。

當我們的鼠標在窗體中移動的時候,會觸發WM_NCHITTEST系統消息,MSDN中對這個消息的說明爲:The WM_NCHITTEST message issent to a window when the cursor moves, or when a mouse button is pressed orreleased. If the mouse is not captured, the message is sent to the windowbeneath the cursor. Otherwise, the message is posted to the window that hascaptured the mouse. (當光標移動或一個鼠標鍵被按下或釋放時,WM_NCHITTEST消息會被髮送到一個窗口中,如果光標沒有被捕獲,這個消息被送到光標下的窗口。否則這個消息被送到捕獲了光標的窗口。)

這個消息被默認的(請注意是“默認的”)窗口過程(窗口過程這個概念後面再說)處理後,會根據觸發這個消息時鼠標的位置返回一個值,例如當鼠標在窗口的標題欄上時,返回HTCAPTION;當鼠標在一個窗口的客戶區中時,返回HTCLIENT;如果鼠標指向某個窗口的字窗口的“關閉”按鈕或系統菜單 (就是點擊窗口圖標後出現的那個菜單),就返回HTSYSMENU。

所以我們要做的就是騙!我們要欺騙Windows,當我們的鼠標在窗體的客戶區中移動時,默認的窗口過程處理後會返回 HTCLIENT,Windows系統根據這個值進行相應的操作,把適當的消息插入到應用程序的消息隊列(這個概念同樣在後面討論)中。這時如果我們做一些改變,人爲地修改窗口過程的返回值,把HTCLIENT修改爲HTCAPTION並返回給系統,系統就會認爲鼠標這時在窗體的標題欄中,而拖動標題欄可以移動一個窗體,所以當我們在一個被這樣修改後的應用程序的客戶區按下鼠標並拖動時,Windows會認我們在拖動一個窗體的標題欄,於是它把一個移動窗體的消息插入到程序的消息隊列中,再經過窗口過程的處理,就實現了我們需要的功能——拖動窗體的客戶區移動窗體。

於是就有了下面的代碼:

protected override WndProc(ref message m)

{

      switch (m.Msg)

      {

         caseWM_NCHITTEST: //如果鼠標移動或單擊                 

           base.WndProc(ref m);//調用基類的窗口過程——WndProc方法處理這個消息

           if (m.Result == (IntPtr)HTCLIENT)//如果返回的是HTCLIENT

           {

                 m.Result = (IntPtr)HTCAPTION;//把它改爲HTCAPTION

                 return;//直接返回退出方法

           }

           break;

      }

      base.WndProc(ref m);//如果不是鼠標移動或單擊消息就調用基類的窗口過程進行處理

}

這也是MSDN上的一個例子,重寫窗體的WndProc方法,判斷消息然後返回。

我本來也是想這樣就完事了,可是後來測試的時候卻發現有個大問題:雙擊窗體的時候,窗體會最大化。

那天腦子有點迷糊,也沒多想就到網上問了一下,一個高手提醒我說可能是那個重寫的窗口過程的問題,於是一下子明白了。我們向Windows傳遞了假信息,Windows我們在窗體的客戶區雙擊鼠標在Windows看來是雙擊了窗體的標題欄,理所當然的Windows會告訴窗口過程窗體需要最大化,窗 口過程又理所當然的照做了,於是就出現了這樣的現象。

剛開始的時候還不是很確定這樣想對不對,不過後來出現的另一個現象證實了這個推斷是正確的:

因爲窗體沒有標題欄,每次退出程序都要按Alt+F4實在是比較不爽,我就給窗體添加了一個ContextMenuStrip,只有一個菜單項用來 關閉窗體退出程序。我發現當我修改了窗體的默認窗口過程後,這個右鍵菜單就顯示不出來了。出現這個現象的原因只能有一個——Windows真的把窗體的客 戶區當成標題欄處理了,而默認ContextMenuStrip只能用在窗體的客戶區或控件上。

後來我又跟那位高手討論了一下,他給我提供了另一種方法,這個方法需要兩個API函數:

LRESULT SendMessage(

HWND hWnd,// handle of destination window

UINT Msg,// message to send

WPARAM wParam,// first message parameter

LPARAM lParam // second message parameter

);

BOOL ReleaseCapture(VOID)

SendMessage函數想必不用多說了,無數個木馬製作教學貼裏都有詳細的描述,這個函數用來向指定的窗體發送Windows消息,功能的確十分強大^_^

ReleaseCapture函數相對陌生一些,這個函數用來釋放被當前線程中某個窗口捕獲的光標。

他給出的代碼是這樣的:

private void Form1_MouseDown(object sender, MouseEventArgse)

{

      ReleaseCapture();

      SendMessage(this.Handle,WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);

}

也就是說,在窗體的MouseDown(鼠標按下)事件中,首先釋放光標,然後向窗口過程發送WM_SYSCOMMAND消息。

說實話這個方式的SendMessage函數調用我也是第一次遇到,剛開始怎麼也不明白爲什麼要把它們(SC_MOVE 和 HTCAPTION)用一個加號連接起來。

這個WM_SYSCOMMAND消息包含了一些信息表示用戶要進行的操作,消息的wParam字段表示用戶要最大化、最小化或移動窗體。

我去掉HTCAPTION試了一下,每當我在窗體上按下鼠標左鍵時,鼠標就會移動到窗體的最頂端——原來標題欄的位置,與我們在系統菜單中選擇“移 動”菜單項的效果一樣。於是查閱MSDN,在WM_SYSCOMMAND的幫助中查到這樣一句話:In WM_SYSCOMMAND messages, the four low-order bits of the uCmdTypeparameter are used internally by Windows. To obtain the correct result whentesting the value of uCmdType, an application must combine the value 0xFFF0with the uCmdType value by using the bitwise AND operator. (在WM_SYSCOMMAND消息中,uCmdType的低四位是Windows內部使用的。如果測試時要獲得uCmdType的正確結果,應用程序必須把0xFFF0這個值與uCmdType的值通過位相加的方式合併在一起。)於是豁然開朗:這個所謂的“Windows內部使用的值”在移動這個窗體的 時候其實就是表示“窗體的標題欄”,所以我們使用SC_MOVE+ HTCAPTION表示窗體將被移動且目前鼠標正在窗體的標題欄中。

至於這裏爲什麼要先釋放光標再發送消息,根據前面的WM_NCHITTEST消息的說明,如果光標被當前窗口捕獲,那麼當鼠標移動時,就會發送 WM_NCHITTEST到我們的窗體,而由於我們這時沒有重寫窗口過程,這個消息會被按照默認的方式處理,Windows會發現鼠標在窗體的客戶區移 動,SendMessage發過去的HTCAPTION被WM_NCHITTEST消息中的HTCLIENT覆蓋,這樣窗體就不會有任何動作。

還有兩個名詞解釋:

窗口過程:窗口過程是一個應用程序定義的、用來處理Windows消息的回調(CallBack)函數,當有消息發生時,窗口過程負責處理消息。

消息隊列:Windows會把有關一個應用程序的全部消息放到一塊內存區域中,這個區域使用一種先進先出(FIFO)的方式工作,就是說最先進入這 個區域的消息會第一個離開這個區域,故稱消息隊列。應用程序從消息隊列中取出一條消息,進行相應的處理,返回一定的信息,然後再取出下一條處理。

例1

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace LiBo.WinControls.Forms {
/// <summary>
/// DragForm 類是可
/// </summary>
public class DragForm : System.Windows.Forms.Form {

private bool dragEnable;
private bool dragging;
private int xOld, yOld;

public DragForm() : base() {
dragEnable = false;
}

/// <summary>
/// 獲取或設置一個值,該值指示窗體是否可以通過鼠標左鍵拖動。
/// </summary>
[DefaultValue(false)]
[Category("Behavior")]
[Description("指示窗體是否可以通過鼠標左鍵拖動")]
public bool DragEnable {
get { return dragEnable; }
set { dragEnable = value; }
}

protected override void OnMouseDown(MouseEventArgs e) {
if(dragEnable && e.Button == MouseButtons.Left) {
// 保存當前鼠標的位置,可以用它來計算鼠標移動的距離
xOld = MousePosition.X;
yOld = MousePosition.Y;
// 標識鼠標正在拖動窗體
dragging = true;
}
}

protected override void OnMouseMove(MouseEventArgs e) {
if(dragEnable && dragging) {
// 計算出鼠標在 X 和 Y 座標方向上移動的距離
int dx = MousePosition.X - xOld;
int dy = MousePosition.Y - yOld;

// 根據上面的結果計算出窗體偏移後的位置
Point point = this.Location;
point.Offset(dx, dy);

// 設置上面的偏移位置爲窗體的位置
this.Location = point;

// 保存當前鼠標的位置,用於下一個循環的計算
xOld = MousePosition.X;
yOld = MousePosition.Y;
}
}

protected override void OnMouseUp(MouseEventArgs e) {
if(dragEnable && e.Button == MouseButtons.Left)
dragging = false;
}
}
}  

例2

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime.InteropServices;

namespace Example013_無標題窗體的拖動
{
/// <summary>
/// Form1 的摘要說明。
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
/// <summary> 



/// 必需的設計器變量。
/// </summary>
private System.ComponentModel.Container components = null;

public Form1()
{
//
// Windows 窗體設計器支持所必需的
//
InitializeComponent();

//
// TODO: 在 InitializeComponent 調用後添加任何構造函數代碼
//
}

/// <summary>
/// 清理所有正在使用的資源。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows Form Designer generated code
/// <summary>
/// 設計器支持所需的方法 - 不要使用代碼編輯器修改
/// 此方法的內容。
/// </summary>
private void InitializeComponent()
{
System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1));
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.BackgroundImage =((System.Drawing.Bitmap)(resources.GetObject("$this.BackgroundImage")));
this.ClientSize = new System.Drawing.Size(292, 273);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Name = "Form1";
this.Text = "Form1";
this.MouseDown += newSystem.Windows.Forms.MouseEventHandler(this.Form1_MouseDown);

}
#endregion

/// <summary>
/// 應用程序的主入口點。
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}

[DllImport("user32.dll")]
public static extern bool ReleaseCapture();
[DllImport("user32.dll")]
public static extern bool SendMessage(IntPtr hwnd,int wMsg,int wParam,int lParam);

public const int WM_SYSCOMMAND=0x0112;
public const int SC_MOVE=0xF010;
public const int HTCAPTION=0x0002;

private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgse)
{
ReleaseCapture();
SendMessage(this.Handle,WM_SYSCOMMAND,SC_MOVE+HTCAPTION, 0);
}
}
}

 


//這篇文章貌似是來自“百度文庫”

發佈了34 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章