研究控件拖拉時,發現當控件爲用戶控件(可以理解成一(yī)個包含多個控件的容器控件),此時拖拉時,不再是點擊控件任意位置能使其移動,隻有當拖動邊界或空白(bái)區域才會有反應。這個問題困擾了我(wǒ)(wǒ)好久,我(wǒ)(wǒ)想過給其中(zhōng)的每個控件都加上拖動方法,或是直接在鼠标捕獲這些控件時都去(qù)調用那個拖動方法,我(wǒ)(wǒ)堅信還有其他辦法,但是從哪裏入手呢???
我(wǒ)(wǒ)們都知(zhī)道windows窗體(tǐ)本身也是一(yī)個容器,也隻能點擊它上面的标題欄讓其移動,如果能實現在窗體(tǐ)上任意位置(包括邊框和用戶區内)随意移動,那麽我(wǒ)(wǒ)這個問題也許能夠得到解決,于是開(kāi)始在GOOGLE上展開(kāi)了全面搜索,發現實現窗體(tǐ)任意移動的方案有幾下(xià)幾種:
1.方案一(yī):在窗體(tǐ)MouseDown事件中(zhōng)添加标題欄移動的消息響應
namespace WindowsApplication1
{
public partial class Form1 : Form
{
[DllImport("user32.dll")] //需添加using System.Runtime.InteropServices
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;
public Form1()
{
InitializeComponent();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
ReleaseCapture();
SendMessage(this.Handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
}
}
}
2.方案二:重載 WndProc 并改寫鼠标事件
首先必須了解Windows的消息傳遞機制,當有鼠标活動消息時,系統發送WM_NCHITTEST 消息給窗體(tǐ)作爲判斷消息發生(shēng)地的根據。假如你點擊的是标題欄,窗體(tǐ)收到的消息值就是 HTCAPTION ,同樣地,若接受到的消息是 HTCLIENT,說明用戶點擊的是客戶區,也就是鼠标消息發生(shēng)在客戶區。
當重載窗體(tǐ)的 WndProc 方法時,可以截獲 WM_NCHITTEST 消息并改些該消息,當判斷鼠标事件發生(shēng)在客戶區時,改寫改消息,發送 HTCAPTION 給窗體(tǐ),這樣,窗體(tǐ)收到的消息就時 HTCAPTION ,在客戶區通過鼠标來拖動窗體(tǐ)就如同通過标題欄來拖動一(yī)樣。
注意:當你重載 WndProc 并改寫鼠标事件後,整個窗體(tǐ)的鼠标事件也就随之改變了。
在Form1類中(zhōng)添加如下(xià)代碼:
private const int WM_NCHITTEST = 0x84;
private const int HTCLIENT = 0x1;
private const int HTCAPTION = 0x2;
//在Form1中(zhōng)改寫鼠标消息
protected override void WndProc(ref Message m)
{
switch(m.Msg)
{
case WM_NCHITTEST:
base.WndProc(ref m);
if ((int)m.Result == HTCLIENT)
m.Result = (IntPtr)HTCAPTION;
return;
break;
}
base.WndProc(ref m);
}
3.方案三:響應基本鼠标事件,捕獲光标移動位置
首先,我(wǒ)(wǒ)們來複習一(yī)下(xià)一(yī)個Windows窗體(tǐ)的組成。它是一(yī)個形式化的标準Windows窗體(tǐ)。首先,窗體(tǐ)的頂部是一(yī)個标題欄,其餘的部分(fēn)是窗體(tǐ)的主體(tǐ),包圍在窗體(tǐ)主體(tǐ)外(wài)圍的是一(yī)個邊框,邊框内不就是我(wǒ)(wǒ)們放(fàng)置控件或繪制圖形的用戶區。
對于用戶區,System.Windows.Forms.Form提供了實例屬性ClientSize,相信大(dà)家已經很熟悉了。而要想活棋一(yī)般性的窗體(tǐ)構造元素(如标題欄、邊框等)的尺寸,我(wǒ)(wǒ)們可以使用.NET類庫中(zhōng)提供的一(yī)個類:System.Windows.Forms.SystemInformation,這個類提供了一(yī)些靜态屬性如表示标題欄高度的CaptionHeight。有關SystemInformation類的信息可以在.NET SDK文檔目錄“.NET Framework SDK -> 參考 -> 類庫 -> System.Windows.Forms -> SystemInformation 類”處找到(注:這裏的超鏈接隻在您安裝了.NET Framework 1.1簡體(tǐ)中(zhōng)文版并且安裝了配套文檔時才有效)。這是一(yī)個很有用的類,希望大(dà)家能夠記住它(可能您早就知(zhī)道了,可我(wǒ)(wǒ)是才知(zhī)道的-_-汗~~)。
接下(xià)來,我(wǒ)(wǒ)們來看看如何在在用戶區拖動鼠标時移動窗體(tǐ)。很容易可以發現:在窗體(tǐ)被拖動的過程中(zhōng),鼠标在窗體(tǐ)内的相對位置是始終不變的!那麽,我(wǒ)(wǒ)們隻要檢測到鼠标在屏幕中(zhōng)的移動并修改窗體(tǐ)的位置就可以達到拖動窗體(tǐ)的目的!
我(wǒ)(wǒ)們知(zhī)道,在鼠标消息/事件處理中(zhōng),隻能得到鼠标相對于窗體(tǐ)的位置。那麽,如何知(zhī)道鼠标在屏幕中(zhōng)的位置呢?這裏又(yòu)要提到一(yī)個類:System.Windows.Forms.Control類。也許你會很吃驚:這不是所有控件的基類麽?呵呵~是這樣di。不過,盡管是這樣,Control類卻沒有像其他廣泛使用的基類那樣被聲明爲抽象類,而且它提供了一(yī)個靜态屬性:MousePosition,通過這個屬性可以得到鼠标相對于屏幕的位置。有關Control類的信息可以在.NET Framework文檔目錄“.NET Framework SDK -> 參考 -> 類庫 -> System.Windows.Forms -> Control 類”處找到(注:這裏的超鏈接隻在您安裝了.NET Framework 1.1簡體(tǐ)中(zhōng)文版并且安裝了配套文檔時才有效)。
知(zhī)道了如何獲取這些信息之後,制作移動窗體(tǐ)實際上就成了一(yī)個很簡單的問題了。基本過程是這樣的:首先,在鼠标(左鍵或一(yī)個你喜歡的鍵)按下(xià)時,記錄鼠标位置;由于在移動的過程中(zhōng),鼠标的屏幕坐标發生(shēng)變化但窗體(tǐ)相對坐标不變,我(wǒ)(wǒ)們可以推算出窗體(tǐ)位置的變化爲(假設mousePosition具有System.Drawing.Point類型,表示鼠标在窗體(tǐ)中(zhōng)的相對坐标):
// 示例代碼1
Form.Top = Control.MousePosition.Y - mousePosition.Y;
Form.Left = Control.MousePosition.X - mousePosition.X;
這樣還不行,因爲我(wǒ)(wǒ)們的mousePosition表示的是鼠标在窗體(tǐ)用戶區内的相對坐标,但在移動窗體(tǐ)的時候還要考慮窗體(tǐ)标題欄和邊框的尺寸。在上面的基礎上,我(wǒ)(wǒ)們将代碼修正爲:
// 示例代碼2
Form.Top = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Height - SystemInformation.CaptionHeight;
Form.Left = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Width;
也就是說,在高度上(縱坐标)要減去(qù)标題欄的高度和邊框的高度,而在寬度上(橫坐标)要減去(qù)邊框的寬度。然而,當制作一(yī)個既沒有标題欄也沒有邊框的可拖動窗體(tǐ)時,使用“示例代碼1”所示的代碼就可以了。
上面的代碼隻是一(yī)個示範性代碼。具體(tǐ)的操作如下(xià):
首先,爲窗體(tǐ)添加一(yī)個私有域:
private System.Drawing.Point mousePoint;
然後,爲窗體(tǐ)添加鼠标按下(xià)事件處理方法(我(wǒ)(wǒ)這裏是MainForm_MouseDown,别忘了将該方法鏈接到MainForm.MouseDown事件,這不用多說了吧?):
private void MainForm_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) {
if(e.Button == MouseButtons.Left) {
this.mousePosition.X = e.X;
this.mousePosition.Y = e.Y;
}
}
在這裏注意對鼠标按鍵進行篩選。
接下(xià)來,爲窗體(tǐ)添加鼠标移動事件處理方法(我(wǒ)(wǒ)這裏是MainForm_MouseMove):
private void MainForm_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) {
if(e.Button == MouseButtons.Left) {
Form.Top = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Height - SystemInformation.CaptionHeight;
Form.Left = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Width;
}
}
上述方案實現的窗體(tǐ)任意移動,仍然有一(yī)定的局限性,當窗體(tǐ)用戶區域包含有很多其他控件時,鼠标點擊在這些控件之上時,窗體(tǐ)仍無法移動,也隻有點擊用戶區的空白(bái)區域時,才會移動。要想點擊任何地方移動,除非給每個控件添加上述類似FORM的處理。所以我(wǒ)(wǒ)開(kāi)始提出的問題,也隻能按照我(wǒ)(wǒ)最初的想法去(qù)解決了。希望有高人來給出其他意見!歡迎大(dà)家前來共同讨論。
我(wǒ)(wǒ)們都知(zhī)道windows窗體(tǐ)本身也是一(yī)個容器,也隻能點擊它上面的标題欄讓其移動,如果能實現在窗體(tǐ)上任意位置(包括邊框和用戶區内)随意移動,那麽我(wǒ)(wǒ)這個問題也許能夠得到解決,于是開(kāi)始在GOOGLE上展開(kāi)了全面搜索,發現實現窗體(tǐ)任意移動的方案有幾下(xià)幾種:
1.方案一(yī):在窗體(tǐ)MouseDown事件中(zhōng)添加标題欄移動的消息響應
namespace WindowsApplication1
{
public partial class Form1 : Form
{
[DllImport("user32.dll")] //需添加using System.Runtime.InteropServices
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;
public Form1()
{
InitializeComponent();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
ReleaseCapture();
SendMessage(this.Handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
}
}
}
2.方案二:重載 WndProc 并改寫鼠标事件
首先必須了解Windows的消息傳遞機制,當有鼠标活動消息時,系統發送WM_NCHITTEST 消息給窗體(tǐ)作爲判斷消息發生(shēng)地的根據。假如你點擊的是标題欄,窗體(tǐ)收到的消息值就是 HTCAPTION ,同樣地,若接受到的消息是 HTCLIENT,說明用戶點擊的是客戶區,也就是鼠标消息發生(shēng)在客戶區。
當重載窗體(tǐ)的 WndProc 方法時,可以截獲 WM_NCHITTEST 消息并改些該消息,當判斷鼠标事件發生(shēng)在客戶區時,改寫改消息,發送 HTCAPTION 給窗體(tǐ),這樣,窗體(tǐ)收到的消息就時 HTCAPTION ,在客戶區通過鼠标來拖動窗體(tǐ)就如同通過标題欄來拖動一(yī)樣。
注意:當你重載 WndProc 并改寫鼠标事件後,整個窗體(tǐ)的鼠标事件也就随之改變了。
在Form1類中(zhōng)添加如下(xià)代碼:
private const int WM_NCHITTEST = 0x84;
private const int HTCLIENT = 0x1;
private const int HTCAPTION = 0x2;
//在Form1中(zhōng)改寫鼠标消息
protected override void WndProc(ref Message m)
{
switch(m.Msg)
{
case WM_NCHITTEST:
base.WndProc(ref m);
if ((int)m.Result == HTCLIENT)
m.Result = (IntPtr)HTCAPTION;
return;
break;
}
base.WndProc(ref m);
}
3.方案三:響應基本鼠标事件,捕獲光标移動位置
首先,我(wǒ)(wǒ)們來複習一(yī)下(xià)一(yī)個Windows窗體(tǐ)的組成。它是一(yī)個形式化的标準Windows窗體(tǐ)。首先,窗體(tǐ)的頂部是一(yī)個标題欄,其餘的部分(fēn)是窗體(tǐ)的主體(tǐ),包圍在窗體(tǐ)主體(tǐ)外(wài)圍的是一(yī)個邊框,邊框内不就是我(wǒ)(wǒ)們放(fàng)置控件或繪制圖形的用戶區。
對于用戶區,System.Windows.Forms.Form提供了實例屬性ClientSize,相信大(dà)家已經很熟悉了。而要想活棋一(yī)般性的窗體(tǐ)構造元素(如标題欄、邊框等)的尺寸,我(wǒ)(wǒ)們可以使用.NET類庫中(zhōng)提供的一(yī)個類:System.Windows.Forms.SystemInformation,這個類提供了一(yī)些靜态屬性如表示标題欄高度的CaptionHeight。有關SystemInformation類的信息可以在.NET SDK文檔目錄“.NET Framework SDK -> 參考 -> 類庫 -> System.Windows.Forms -> SystemInformation 類”處找到(注:這裏的超鏈接隻在您安裝了.NET Framework 1.1簡體(tǐ)中(zhōng)文版并且安裝了配套文檔時才有效)。這是一(yī)個很有用的類,希望大(dà)家能夠記住它(可能您早就知(zhī)道了,可我(wǒ)(wǒ)是才知(zhī)道的-_-汗~~)。
接下(xià)來,我(wǒ)(wǒ)們來看看如何在在用戶區拖動鼠标時移動窗體(tǐ)。很容易可以發現:在窗體(tǐ)被拖動的過程中(zhōng),鼠标在窗體(tǐ)内的相對位置是始終不變的!那麽,我(wǒ)(wǒ)們隻要檢測到鼠标在屏幕中(zhōng)的移動并修改窗體(tǐ)的位置就可以達到拖動窗體(tǐ)的目的!
我(wǒ)(wǒ)們知(zhī)道,在鼠标消息/事件處理中(zhōng),隻能得到鼠标相對于窗體(tǐ)的位置。那麽,如何知(zhī)道鼠标在屏幕中(zhōng)的位置呢?這裏又(yòu)要提到一(yī)個類:System.Windows.Forms.Control類。也許你會很吃驚:這不是所有控件的基類麽?呵呵~是這樣di。不過,盡管是這樣,Control類卻沒有像其他廣泛使用的基類那樣被聲明爲抽象類,而且它提供了一(yī)個靜态屬性:MousePosition,通過這個屬性可以得到鼠标相對于屏幕的位置。有關Control類的信息可以在.NET Framework文檔目錄“.NET Framework SDK -> 參考 -> 類庫 -> System.Windows.Forms -> Control 類”處找到(注:這裏的超鏈接隻在您安裝了.NET Framework 1.1簡體(tǐ)中(zhōng)文版并且安裝了配套文檔時才有效)。
知(zhī)道了如何獲取這些信息之後,制作移動窗體(tǐ)實際上就成了一(yī)個很簡單的問題了。基本過程是這樣的:首先,在鼠标(左鍵或一(yī)個你喜歡的鍵)按下(xià)時,記錄鼠标位置;由于在移動的過程中(zhōng),鼠标的屏幕坐标發生(shēng)變化但窗體(tǐ)相對坐标不變,我(wǒ)(wǒ)們可以推算出窗體(tǐ)位置的變化爲(假設mousePosition具有System.Drawing.Point類型,表示鼠标在窗體(tǐ)中(zhōng)的相對坐标):
// 示例代碼1
Form.Top = Control.MousePosition.Y - mousePosition.Y;
Form.Left = Control.MousePosition.X - mousePosition.X;
這樣還不行,因爲我(wǒ)(wǒ)們的mousePosition表示的是鼠标在窗體(tǐ)用戶區内的相對坐标,但在移動窗體(tǐ)的時候還要考慮窗體(tǐ)标題欄和邊框的尺寸。在上面的基礎上,我(wǒ)(wǒ)們将代碼修正爲:
// 示例代碼2
Form.Top = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Height - SystemInformation.CaptionHeight;
Form.Left = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Width;
也就是說,在高度上(縱坐标)要減去(qù)标題欄的高度和邊框的高度,而在寬度上(橫坐标)要減去(qù)邊框的寬度。然而,當制作一(yī)個既沒有标題欄也沒有邊框的可拖動窗體(tǐ)時,使用“示例代碼1”所示的代碼就可以了。
上面的代碼隻是一(yī)個示範性代碼。具體(tǐ)的操作如下(xià):
首先,爲窗體(tǐ)添加一(yī)個私有域:
private System.Drawing.Point mousePoint;
然後,爲窗體(tǐ)添加鼠标按下(xià)事件處理方法(我(wǒ)(wǒ)這裏是MainForm_MouseDown,别忘了将該方法鏈接到MainForm.MouseDown事件,這不用多說了吧?):
private void MainForm_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) {
if(e.Button == MouseButtons.Left) {
this.mousePosition.X = e.X;
this.mousePosition.Y = e.Y;
}
}
在這裏注意對鼠标按鍵進行篩選。
接下(xià)來,爲窗體(tǐ)添加鼠标移動事件處理方法(我(wǒ)(wǒ)這裏是MainForm_MouseMove):
private void MainForm_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) {
if(e.Button == MouseButtons.Left) {
Form.Top = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Height - SystemInformation.CaptionHeight;
Form.Left = Control.MousePosition.Y - mousePosition.Y
- SystemInformation.FrameBorderSize.Width;
}
}
上述方案實現的窗體(tǐ)任意移動,仍然有一(yī)定的局限性,當窗體(tǐ)用戶區域包含有很多其他控件時,鼠标點擊在這些控件之上時,窗體(tǐ)仍無法移動,也隻有點擊用戶區的空白(bái)區域時,才會移動。要想點擊任何地方移動,除非給每個控件添加上述類似FORM的處理。所以我(wǒ)(wǒ)開(kāi)始提出的問題,也隻能按照我(wǒ)(wǒ)最初的想法去(qù)解決了。希望有高人來給出其他意見!歡迎大(dà)家前來共同讨論。