windows窗口樣式
windows下都有相對固定的窗體樣式,當然這些樣式可能會根據windows主題會有很大的變換,就像皮膚一樣。但windows也提供了一些API,來允許我們修改默認的窗體樣式,以實現各自的樣式需求,如《C#重繪Windows窗體標題欄和邊框》示例。windows在各不同版本的操作系統,各種不同主題下,窗體的樣式會有很大不同,除了標題字體,顏色的變化,還有窗體的形狀。如有些是圓角矩形、有些是實角矩形;有些窗體邊框較寬,有些較窄;有些標題欄較高,有些則較矮,等等。而窗體的是否為圓角矩形,我在《Setwindowrgn函數應用--截圖,繪制多邊形窗體》中已經有寫過,修改窗體形狀的方法。而本文主要針對窗體的邊框厚度,和標題欄的高度如何修改,提出一些解決方案。因為windows應用程序在不同版本的windows操作系統和主題下通常都表現不同的外觀,因此要想保持外觀的一致,就必須了解窗體的形狀及邊框和標題欄高度的修改的方法,這樣才能保證統一。
WM_NCCALCSIZE消息
當windows客戶區的大小和位置需要重新計算時會發出該消息,因此,攔截該消息可以控制windows窗體客戶區的尺寸和位置。WM_NCCALCSIZE的定義為:
[c++]
#define WM_NCCALCSIZE 0x0083
C#定義為:
[c#]
const int WM_NCCALCSIZE = 0x0083;
並且當消息參數wParam為TRUE時,lParam參數為NCCALCSIZE_PARAMS類型的指針,該類型包含了可用於計算客戶區大小和位置的信息。NCCALCSIZE_PARAMS的簽名:
[c++]
typedef struct tagNCCALCSIZE_PARAMS {
RECT rgrc[3];
PWINDOWPOS lppos;
} NCCALCSIZE_PARAMS, *LPNCCALCSIZE_PARAMS;
C#聲明:
[c#]
struct NCCALCSIZE_PARAMS {
public _RECT rcNewWindow;
public _RECT rcOldWindow;
public _RECT rcClient;
IntPtr lppos;
}
struct _RECT {
public int left;
public int top;
public int right;
public int bottom;
}
NCCALCSIZE_PARAMS結構體
C#對NCCALCSIZE_PARAMS的聲明與C/C++的聲明看似有一點點不同,C/C++定義的是RECT數組,而C#則變成了三個單獨的RECT字段,當前效果是一樣的,只不過一個使用[]來所有對象,而一個是自己訪問而已。也就是前者的rgrc[0]與後者的rcNewWindow對應,前者的rgrc[1]與rcOldWindow對應,和前者的rgrc[2]與rcClient對應。只所有C#分別定義,主要是為了更好的說明這三個RECT分別表示的意思。
第一個RECT或rcNewWindow存儲了窗體被移動或尺寸被修改後的窗體的坐標信息,這是窗口馬上要應用的信息,即窗口馬上就移動到坐標(rcNewWindow.left, rcNewWindow.top),並且尺寸將調整為(rcNewWindow.right-rcNewWindow.left, rcNewWindow.bottom-rcNewWindow.top);而第二個RECT或rcOldWindow存儲移動前或尺寸被改變前窗體的坐標信息;第三個RECT或rcClient存儲了移動前或尺寸修改前客戶區的坐標信息。如果窗體是子窗體,那麼坐標信息都使相對父窗體的,否則坐標是相對於屏幕的。然而,當消息處理完畢後,第一個RECT或rcNewWindow則被用來存儲移動後或大小改變後的客戶區的坐標信息,即計算夠的結果。也可以推測出計算過程使用rcNewWindow坐標與當前操作系統和主題下的窗體標題欄的高度和窗體邊框的厚度等信息計算得來。假設窗體高度為CaptionHeight,窗體邊框厚度為BorderWidth,那麼客戶區就可以通過公式計算得出客戶區域坐標:
[c#]
client.left = rcNewWindow.left + BorderWidth
client.top = rcNewWindow.top + BorderWidth + CaptionHeight
client.right = rcNewWindow.right - BorderWidth
client.bottom = rcNewWindow.bottom - BorderWidth
其實窗體邊框厚度和窗體高度都可以從類System.Windows.Forms.SystemInformation中獲取,不過要根據窗體類型和主題等選擇不同的厚度和高度,這裡不再詳細說明。你可以在窗口創建時攔截WM_NCCALCSIZE消息,獲取NCCALCSIZE_PARAMS參數,並根據rcNewWindow和rcClient來計算窗體邊框厚度和標題欄高度。
修改客戶區域
前面的文字目的,主要是為了說明客戶區域的坐標信息,是根據窗體的新坐標信息、窗體邊框厚度和標題欄的高度進行計算得來的。因此我們可以攔截WM_NCCALCSIZE消息,人為的修改窗體的坐標信息,從而影響客戶區坐標信息的計算結果。
由前面說的公式可以看出,對新窗口坐標的增減就是對客戶區坐標的增減,因此,如下攔截消息代碼,實現了加寬,加高客戶區的效果:
[c#]
private void AdjustClientRect(ref _RECT rcClient) {
rcClient.left -= 3;
rcClient.right += 3;
rcClient.bottom += 3;
}
const int WM_NCCALCSIZE = 0x0083;
protected override void WndProc(ref Message m) {
switch (m.Msg) {
case WM_NCCALCSIZE: {
if (m.WParam != IntPtr.Zero) {
NCCALCSIZE_PARAMS rcsize = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure
(m.LParam, typeof(NCCALCSIZE_PARAMS));
AdjustClientRect(ref rcsize.rcNewWindow);
Marshal.StructureToPtr(rcsize, m.LParam, false);
}
m.Result = new IntPtr(1);
}
break;
}
base.WndProc(ref m);
}
由於客戶區的寬和高都增加了,但窗體大大小和位置並沒有變化,因此運行的效果就如圖所示,左右和下邊框的厚度明顯變小,而標題欄沒有變化,因為我們沒有調整rcClient.top字段。
因此,如果你再重繪窗體,希望窗體在各版本的windows操作系統和主題中表現一致,可以希望保持窗體的版塊厚度為BorderWidth,你就可以這樣來調整代碼:
[c#]
int BorerWidth = 1;
private void AdjustClientRect(ref _RECT rcClient) {
rcClient.left -= SystemInformation.FrameBorderSize.Width - BorerWidth;
rcClient.right += SystemInformation.FrameBorderSize.Width - BorerWidth;
rcClient.bottom += SystemInformation.FrameBorderSize.Width - BorerWidth;
}
這裡也沒有設置rcClient.top,以為窗體頂部的邊框在標題欄上訪,這裡簡單的修改rcClient.top是達不到預期效果的,需要重繪標題欄才行,可以參見《C#重繪Windows窗體標題欄和邊框》。運行效果如下:
當然,你也可以利用這個方法,在不設置FormBorderStyle屬性為None的情況下,實現無邊框窗口的效果。簡單修改代碼即可實現:
[c#]
private void AdjustClientRect(ref _RECT rcClient) {
rcClient.left -= SystemInformation.FrameBorderSize.Width;
rcClient.right += SystemInformation.FrameBorderSize.Width;
rcClient.bottom += SystemInformation.FrameBorderSize.Width;
rcClient.top -= SystemInformation.FrameBorderSize.Width + SystemInformation.CaptionHeight;
}