獲得MDI客戶端的“控制代碼”
內容 介紹 MdiClient控制 一個參考能做什麼 改變背景顏色 一個控制代碼可以做什麼 改變邊界樣式 視窗訊息能做什麼 NativeWindow類 隱藏滾動條 先進的繪畫 MdiClientController元件 使用元件 結論 介紹 在最近的一個專案,我決定使用一個多文件介面(MDI)將是最好的方法。我驚訝於有多容易建立MDI應用程式在Visual Studio . net平臺。簡單地設定IsMdiContainer System.Windows.Forms的財產。表單允許託管應用程式中其他形式的工作區。然而,如果你像我一樣,你開始想知道,工作區將會看起來像一個不同的顏色,定製繪畫,或者不同的邊框樣式。我很快發現,表單控制接觸沒有這樣的屬性來控制這種行為。搜尋的網路顯示,許多人想要做同樣的事情,有各種方法關於如何做到這一點。在使用他們的建議成功在我的應用程式和建立幾個我自己的,我決定收集所有這些資訊在一個地方,也許開發元件,允許輕鬆地設定這些屬性。 MdiClient控制 事實證明,MDI的Windows®只是另一種控制形式。當IsMdiContainer屬性設定為true, System.Windows.Forms型別的控制。MdiClient新增到控制元件的集合形式。遍歷表單的控制載入後將揭示MdiClient控制,也可能是最好的方式參考。MdiClient控制有一個公共建構函式,可以被新增到表單的控制程式設計集合,但更好的做法是設定表單的IsMdiContainer屬性並讓它做這項工作。設定參考MdiClient控制、迭代控制直到MdiClient控制發現:隱藏,複製Code
MdiClient mdiClient = null; // Get the MdiClient from the parent form. for(int i = 0; i < parentForm.Controls.Count; i++) { // If the form is an MDI container, it will contain an MdiClient control // just as it would any other control. mdiClient = parentForm.Controls[i] as MdiClient; if(mdiClient != null) { // The MdiClient control was found. // ... // break; } }
使用關鍵字是比直接在一個try / catch塊或使用關鍵字,因為如果匹配型別,參考控制將結果或將返回null。這就像兩個要求的價格。 注意:在測試中,我發現可以新增多個MdiClient控制表單的控制集合。在這種情況下,只有一個MdiClient控制元件將會作為主機和這段程式碼可能會失敗,返回一個引用MdiClient不執行託管孩子的形式。一個很好的理由使用IsMdiContainer屬性而不是增加手動控制形式。 一個參考能做什麼 改變背景顏色 與參考MdiClient控制在手,許多常見的控制屬性可以如你所願。通常要求當然是改變背景顏色。應用程式的預設背景顏色工作區是全球所有的Windows®應用程式,可以在控制面板中改變。. net框架System.Drawing.SystemColors暴露了這種顏色。AppWorkspace靜態屬性。改變背景顏色是如你所願,通過背景色屬性:隱藏,複製Code
// Set the color of the application workspace. mdiClient.BackColor = value;
,以及許多其他控制元件屬性將與MdiClient控制按預期工作。 一個控制代碼可以做什麼 改變邊界樣式 缺席MdiClient控制,然而,是一個邊框樣式屬性。典型System.Windows.Forms一去不復返了。邊框樣式的列舉選項Fixed3D FixedSingle,沒有。預設情況下,應用程式的工作區MDI形式是插圖的3 d邊界相當於Fixed3D是什麼。只是因為這種行為並不是公開的控制並不意味著它是不可訪問的。從這裡開始,您將看到MdiClient變成更有價值的處理只是一個參考。 改變邊界的外觀要求使用Win32函式呼叫。(更多這方面的資訊可以從傑森失去的文章:向用戶控制元件新增可被識別的邊界)。每個視窗(例如,控制)在Windows®可以通過使用GetWindowLong檢索資訊,並設定通過使用SetWindowLong函式。功能都需要一個標誌,指定哪些資訊我們想獲取和設定。在這種情況下,我們感興趣的是GWL_STYLE GWL_EXSTYLE,獲取和設定視窗的風格和擴充套件視窗風格標誌,分別。因為這些更改的非客戶區控制,呼叫控制元件的無效方法不會導致邊界重新粉刷。相反,我們呼叫SetWindowPos函式導致一個更新的非客戶區。這些函式和常量的定義是這樣的:隱藏,收縮,複製Code
// Win32 Constants private const int GWL_STYLE = -16; private const int GWL_EXSTYLE = -20; private const int WS_BORDER = 0x00800000; private const int WS_EX_CLIENTEDGE = 0x00000200; private const uint SWP_NOSIZE = 0x0001; private const uint SWP_NOMOVE = 0x0002; private const uint SWP_NOZORDER = 0x0004; private const uint SWP_NOREDRAW = 0x0008; private const uint SWP_NOACTIVATE = 0x0010; private const uint SWP_FRAMECHANGED = 0x0020; private const uint SWP_SHOWWINDOW = 0x0040; private const uint SWP_HIDEWINDOW = 0x0080; private const uint SWP_NOCOPYBITS = 0x0100; private const uint SWP_NOOWNERZORDER = 0x0200; private const uint SWP_NOSENDCHANGING = 0x0400; // Win32 Functions [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern int GetWindowLong(IntPtr hWnd, int Index); [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern int SetWindowLong(IntPtr hWnd, int Index, int Value); [DllImport("user32.dll", ExactSpelling = true)] private static extern int SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
注意:在定義這些常數的值h標頭檔案,通常由平臺SDK或Visual Studio . net安裝。 我們可以根據BorderStyle列舉來調整邊框:在擴充套件的視窗樣式(Fixed3D)中包含一個WS_EX_CLIENTEDGE標誌,在標準視窗樣式(FixedSingle)中包含一個WS_BORDER標誌,或者為無邊框刪除這兩個標誌(None)。然後呼叫SetWindowPos函式導致更新。SetWindowPos函式有很多選項,但我們只想重新繪製非客戶區,並將傳遞必要的標誌來完成此工作:Hide 收縮,複製Code
// Get styles using Win32 calls int style = GetWindowLong(mdiClient.Handle, GWL_STYLE); int exStyle = GetWindowLong(mdiClient.Handle, GWL_EXSTYLE); // Add or remove style flags as necessary. switch(value) { case BorderStyle.Fixed3D: exStyle |= WS_EX_CLIENTEDGE; style &= ~WS_BORDER; break; case BorderStyle.FixedSingle: exStyle &= ~WS_EX_CLIENTEDGE; style |= WS_BORDER; break; case BorderStyle.None: style &= ~WS_BORDER; exStyle &= ~WS_EX_CLIENTEDGE; break; } // Set the styles using Win32 calls SetWindowLong(mdiClient.Handle, GWL_STYLE, style); SetWindowLong(mdiClient.Handle, GWL_EXSTYLE, exStyle); // Update the non-client area. SetWindowPos(mdiClient.Handle, IntPtr.Zero, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
視窗訊息能做什麼 NativeWindow類 除了更改簡單的屬性或進行Win32呼叫外,要進入定製領域,我們需要攔截和處理視窗訊息。不幸的是,MdiClient類是密封的,因此不能被子類化,也不能覆蓋它的WndProc方法。值得慶幸的是,System.Windows.Forms。NativeWindow類來解決這個問題。NativeWindow類的目的是提供“視窗控制代碼和視窗過程的低階封裝”。換句話說,它允許我們進入控制元件接收到的視窗訊息。要使用NativeWindow,從類繼承並覆蓋它的WndProc方法。一旦通過AssignHandle方法將控制元件的控制代碼分配給NativeWindow, WndProc方法的行為就像它是控制元件的WndProc方法一樣。通過偵聽MdiClient控制元件的視窗訊息,可以實現一系列全新的定製。 隱藏滾動條 雖然通過滾動條訪問應用程式工作區之外的控制元件是一個很好的特性,但我個人不記得我使用過的MDI應用程式有同樣的功能。在MdiClient中關閉或隱藏滾動條的功能可能比更改其顏色更常見。 MdiClient控制元件的滾動條是它的非客戶區域(client矩形之外的區域)的一部分,它們本身不是MdiClient的父控制元件。這就排除了更改滾動條可見性的可能性,並給我們留下了影響非客戶區大小的視窗訊息和Win32函式。當需要計算控制元件的非客戶區域時,將向控制元件傳送WM_NCCALCSIZE訊息。為了隱藏滾動條,我們可以告訴Windows®,非客戶區比它實際的面積小一點,並蓋住滾動條。我的第一個方法是嘗試確定非客戶區域的大小,但失敗了。更好的方法是在使用ShowScrollBar Win32函式計算非客戶區時隱藏滾動條。ShowScrollBar函式需要視窗控制代碼、隱藏的滾動條和指示其可見性的bool:複製Code
// Win32 Constants private const int SB_HORZ = 0; private const int SB_VERT = 1; private const int SB_CTL = 2; private const int SB_BOTH = 3; // Win32 Functions [DllImport("user32.dll")] private static extern int ShowScrollBar(IntPtr hWnd, int wBar, int bShow); protected override void WndProc(ref Message m) { switch(m.Msg) { // // ... // case WM_NCCALCSIZE: ShowScrollBar(m.HWnd, SB_BOTH, 0 /*false*/); break; } base.WndProc(ref m); }
在隱藏了滾動條之後,WM_NCCALCSIZE訊息像往常一樣被處理並且計算非客戶區域減去最近隱藏的滾動條。如果你想知道,通過ShowScrollBar函式隱藏滾動條不會保持滾動條隱藏,它會立即重置為可見。這就是為什麼每次計算非客戶區時必須隱藏它的原因。 先進的繪畫 在web的。net論壇中,我看到的另一個常見請求是,“如何將影象放到MDI表單的應用程式工作區中?”最簡單的方法是,一旦有了對MdiClient的引用,就偵聽Paint事件。在某些情況下,這可能工作良好,但我注意到一個非常糟糕的閃爍每次MdiClient調整大小。這是繪製沒有被雙緩衝和繪製呼叫都在WM_PAINT和WM_ERASEBKGND訊息的結果。如果我們能夠從MdiClient控制元件繼承,這可以通過使用該控制元件的受保護方法SetStyle和標誌system . window . forms . controlstyles來輕鬆補救。AllPaintingInWmPaint ControlStyles。DoubleBuffer, ControlStyles.UserPaint。但如前所述,MdiClient類是密封的,這不是一個選項。一個選項是監聽WM_PAINT和WM_ERASEBKGND視窗訊息並實現我們自己的自定義繪製。(更多資訊請參閱Steve McMahon的文章:MDI客戶區的繪畫)。 我們需要的Win32專案是函式BeginPaint和EndPaint,稱為PAINTSTRUCT和RECT的structs,以及一些更多的常量:Hide收縮,複製Code
// Win32 Constants private const int WM_PAINT = 0x000F; private const int WM_ERASEBKGND = 0x0014; private const int WM_PRINTCLIENT = 0x0318; // Win32 Structures [StructLayout(LayoutKind.Sequential, Pack = 4)] private struct PAINTSTRUCT { public IntPtr hdc; public int fErase; public RECT rcPaint; public int fRestore; public int fIncUpdate; [MarshalAs(UnmanagedType.ByValArray, SizeConst=32)] public byte[] rgbReserved; } [StructLayout(LayoutKind.Sequential)] private struct RECT { public int left; public int top; public int right; public int bottom; } // Win32 Functions [DllImport("user32.dll")] private static extern IntPtr BeginPaint(IntPtr hWnd, ref PAINTSTRUCT paintStruct); [DllImport("user32.dll")] private static extern bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT paintStruct);
雙緩衝的典型方法是對影象進行所有繪製,或者從影象中獲取圖形物件,而不是直接繪製到螢幕上。當繪製圖像完成時,影象本身被繪製到螢幕上。這樣,所有控制元件的繪製都是一次顯示,而不是可能正在進行的間歇繪製。由於MdiClient控制元件的圖形如此簡單,我們可以很容易地自己繪製所有的圖形,but更好的做法是不能消除的基本圖形繪製,但將它們新增到我們的定製繪畫。這樣,如果MdiClient改變在某種程度上我們沒有預料,這幅畫仍應正確顯示。這是通過建立自己的視窗訊息(WM_PRINTCLIENT),並將其傳送給基地控制使用DefWndProc(即違約指向)方法。我們回到原始圖形緩衝區是一幅畫的畫的基本控制。(這可以從J年輕的文章:生成失蹤油漆TreeView和ListView控制元件的事件。)從那裡,任何自定義畫在上面可以處理:隱藏,收縮,複製Code
protected override void WndProc(ref Message m) { switch(m.Msg) { //Do all painting in WM_PAINT to reduce flicker. case WM_ERASEBKGND: return; case WM_PAINT: // Use Win32 to get a Graphics object. PAINTSTRUCT paintStruct = new PAINTSTRUCT(); IntPtr screenHdc = BeginPaint(m.HWnd, ref paintStruct); using(Graphics screenGraphics = Graphics.FromHdc(screenHdc)) { // Double-buffer by painting everything to an image and // then drawing the image. int width = (mdiClient.ClientRectangle.Width > 0 ? mdiClient.ClientRectangle.Width : 0); int height = (mdiClient.ClientRectangle.Height > 0 ? mdiClient.ClientRectangle.Height : 0); using(Image i = new Bitmap(width, height)) { using(Graphics g = Graphics.FromImage(i)) { // Draw base graphics and raise the base Paint event. IntPtr hdc = g.GetHdc(); Message printClientMessage = Message.Create(m.HWnd, WM_PRINTCLIENT, hdc, IntPtr.Zero); DefWndProc(ref printClientMessage); g.ReleaseHdc(hdc); // // Custom painting here... // } // Now draw all the graphics at once. screenGraphics.DrawImage(i, mdiClient.ClientRectangle); } } EndPaint(m.HWnd, ref paintStruct); return; } base.WndProc(ref m); }
注意:更多資訊BeginPaint、EndPaint PAINTSTRUCT,矩形,可以找到WM_PRINTCLIENT平臺SDK或MSDN圖書館。 請注意,在這種情況下,我們不讓WM_PAINT訊息處理失敗的基礎,因為這會使它指向做其預設畫就在我們剛剛做的自己。WM_ERASEBKGND訊息被忽略因為我們想做所有的畫一次WM_PAINT訊息。現在,MdiClient控制油漆事件將不再閃爍和自定義畫程式碼可以放在上面WM_PAINT訊息的處理。 MdiClientController元件 而不必把這段程式碼放到每一個專案都使用多文件介面,我們可以結束這一切System.ComponentModel。元件可以從專案複製到設計圖面。原始檔是一個元件中包括我叫Slusser MdiClientController和發現。元件名稱空間。元件繼承自NativeWindow並實現了System.ComponentModel。託管介面給它元件的行為。它包含的所有功能前面討論的一些屬性,這些屬性使它容易的地方一個影象在應用程式的工作區。 使用元件與一個MDI形式,只有父窗體必須傳遞給建構函式或通過ParentForm屬性設定。設定MdiClientController元件的ParentForm屬性的設計師,我們需要定製站點屬性來確定元件放到一個表格。它有助於在這裡有一個設計師的知識。如果確實將元件放到表單,我們設定ParentForm屬性,它是設計師正確序列化程式碼:隱藏,複製Code
public ISite Site { get { return site; } set { site = value; if(site == null) return; // If the component is dropped onto a form during design-time, // set the ParentForm property. IDesignerHost host = (value.GetService(typeof(IDesignerHost)) as IDesignerHost); if(host != null) { Form parent = host.RootComponent as Form; if(parent != null) ParentForm = parent; } } }
建立這個元件的一個挑戰是知道什麼時候該元件將被初始化。中初始化元件放到設計師InitializeComponent形式的建構函式的方法。如果你檢查InitializeComponent方法建立的設計師,你會注意到表單的屬性設定的最後一件事。如果MdiClientController掃描MdiClient控制形式的控制收集表單的IsMdiContainer屬性設定之前,沒有MdiClient控制會被發現。解決方案是建立知道當父窗體的控制代碼。這肯定會顯示所有子控制元件和變數被初始化,當我們可以開始尋找MdiClient。如果父窗體沒有處理ParentForm屬性設定時,該元件會聽表單的HandleCreated事件然後得到MdiClient:隱藏,收縮,複製Code
public Form ParentForm { get { return parentForm; } set { // If the ParentForm has previously been set, // unwire events connected to the old parent. if(parentForm != null) parentForm.HandleCreated -= new EventHandler(ParentFormHandleCreated); parentForm = value; if(parentForm == null) return; // If the parent form has not been created yet, // wait to initialize the MDI client until it is. if(parentForm.IsHandleCreated) { InitializeMdiClient(); RefreshProperties(); } else parentForm.HandleCreated += new EventHandler(ParentFormHandleCreated); } } private void ParentFormHandleCreated(object sender, EventArgs e) { // The form has been created, unwire the event, // and initialize the MdiClient. parentForm.HandleCreated -= new EventHandler(ParentFormHandleCreated); InitializeMdiClient(); RefreshProperties(); }
使用元件 一旦MdiClientController被新增到工具箱,直接拖到形式的設計師,或者雙擊它,它將顯示在元件托盤的設計師。MdiClientController不會改變形式的IsMdiContainer財產,所以你必須設定它。所有元件的屬性遵循. net命名約定。邊框樣式功能是包裹在邊框樣式屬性。滾動條的隱藏,我想,是最好的屬性放在一個可以自動滾動。背景色和油漆事件現在可以從設計師為您的方便。此外,有三個屬性,控制影象的顯示在客戶區。影象屬性集影象的顯示,ImageAlign屬性將在客戶區不同位置,和StretchImage屬性將它填滿整個客戶區。此外,我添加了一個HandleAssigned事件表明當MdiClient被發現和處理分配到NativeWindow。當然,這一切都可以通過程式設計的方式完成。 結論 和許多專案,成為文章一樣,我有我最初需要大約30分鐘,但是花了幾天準備的東西我可以分享我的程式設計師。生成的元件應該足夠了對於大多數請求關於MDI的外觀形式。效果很好,它表現好,讓應用程式看起來不錯(ly)。還有更多,可以新增到元件,如果需要,我相信一些程式設計師,它將。或障礙,而有一個特點,我謙卑地承認我並不是能夠克服:設計時預覽。使用反射器,我發現大量的路障,防止設計時MDI的預覽區域。我歡迎任何建議如何克服這個問題。享受。 本文轉載於:http://www.diyabc.com/frontweb/news5088.html