前言
這個系列好久都沒有更新了,貌似上一篇還是在今年五月份發布的。呵呵,不感慨了,還是開始介紹今天的內容吧! 這裡說明一下這次實現的換膚都是基於貼圖換膚的,並不可以像QQ那樣還可以調整色調甚至自訂圖片為背景。如果您已經有過這方面的經驗,下面的內容或許不一定適合你。另外如果您對本文有興趣請到最後下載源碼對照閱讀。如果您還沒有看過這個系列前續的文章請先參閱這裡,本文的內容是在那幾篇的基礎上建立的!
貼圖換膚就是用不同的圖片去畫不同的地方的背景,最後形成了介面的一個整體樣式外觀(自己這樣認為的,如有不對歡迎拍磚! )。只要我們將每個背景圖片的位置以及大小資訊記錄下來,並在換膚的時候載入這些圖片和資訊並將它們畫到背景上去就能實現換膚了。很簡單吧~~
最終的:
換膚實現
上面只是簡單說了一下換膚的“原理”,下面這個換膚流程圖或許能夠協助您更好理解它:
上面的這四個過程就對應了實際類中的四個主要方法:ReadIniFile,CaculatePartLocation,ReadBitmap和DrawBackground。我們一個一個來看:
ReadIniFile
這個方法主要用來讀取皮膚配置資訊。 什嗎?不知道配置資訊長啥樣?得了,那就給你看一眼吧。注釋的都很明白了,相信大家都明白了吧!我也就不羅嗦了(上次有人說我寫的太囉嗦,我是痛定思痛啊,呵呵)
這是個INI的設定檔,網上有很多方法教你怎麼讀取和寫入INI了。我將它們封裝成了ReadIniValue和WriteIniValue方法。讀取出來的這些資訊都放在各自的背景塊變數中。這裡所說的背景塊就是前面一篇文章介紹到的將介面劃分的九個地區,如果您不瞭解可以看看這個系列的前一篇文章。一個背景塊就是一個類,這些類都繼承於partbase基類。partbase類中就定義了設定檔中對應的幾個變數和幾個方法,具體的可以到原始碼中查看這個類,不複雜。
代碼 private bool ReadIniFile(string skinFolder) { try { string filePath = skinFolder + "\\config.ini"; //頂部 _topLeft.Height = _topMiddle.Height = _topRight.Height = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "Top_Height")); _topLeft.Width = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "TopLeft_Width")); _topRight.Width = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "TopRight_Width")); //底部 _bottomLeft.Height = _bottomMiddle.Height = _bottomRight.Height = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "Bottom_Height")); _bottomLeft.Width = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "BottomLeft_Width")); _bottomRight.Width = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "BottomRight_Width")); //中部 _centerLeft.Width = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "MiddleLeft_Width")); _centerRight.Width = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "MiddleRight_Width")); minButton.Width = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "MinButton_Width")); minButton.Height = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "MinButton_Height")); minButton.XOffset = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "MinButton_X")); minButton.Top = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "MinButton_Y")); maxButton.Width = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "MaxButton_Width")); maxButton.Height = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "MaxButton_Height")); maxButton.XOffset = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "MaxButton_X")); maxButton.Top = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "MaxButton_Y")); closeButton.Width = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "CloseButton_Width")); closeButton.Height = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "CloseButton_Height")); closeButton.XOffset = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "CloseButton_X")); closeButton.Top = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "CloseButton_Y")); selectSkinButton.Width = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "selectSkinButton_Width")); selectSkinButton.Height = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "selectSkinButton_Height")); selectSkinButton.XOffset = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "selectSkinButton_X")); selectSkinButton.Top = int.Parse(IniHelper.ReadIniValue(filePath, "Main", "selectSkinButton_Y")); return true; } catch { return false; } }
CaculatePartLocation
在這個方法中就是根據當前表單的width和height,再加上讀取到的配置資訊計算各個背景塊的大小和位置。請注意方法中計算的順序,先是左上方然後右上方最後中間。因為為了實現表單的可縮放,中間的寬度是可變的。然後是左下方右下方,最後才是中間。中間的內容地區被我放了一個panel,其大小是可以變化的,具體的大小位置要看計算的結果。panel的另一個作用就是實現放在裡面的控制項的布局更好設定而不必關心上下兩個邊框。
代碼 private void CaculatePartLocation() { //頂部 _topLeft.X = 0; _topLeft.Y = 0; _topRight.X = Width - _topRight.Width; _topRight.Y = 0; _topMiddle.X = _topLeft.Width; _topMiddle.Y = 0; _topMiddle.Width = Width - _topLeft.Width - _topRight.Width; //中間部分 _centerLeft.X = 0; _centerLeft.Y = _topLeft.Height; _centerLeft.Height = Height - _topLeft.Height - _bottomLeft.Height; _centerRight.X = Width - _centerRight.Width; _centerRight.Y = _topRight.Height; _centerRight.Height = Height - _topLeft.Height - _bottomLeft.Height; _centerMiddle.X = _centerLeft.Width; _centerMiddle.Y = _topMiddle.Height; _centerMiddle.Width = Width - _centerLeft.Width - _centerRight.Width; _centerMiddle.Height = Height - _topMiddle.Height - _bottomMiddle.Height; //底部 _bottomLeft.X = 0; _bottomLeft.Y = Height - _bottomLeft.Height; _bottomRight.X = Width - _bottomRight.Width; _bottomRight.Y = Height - _bottomRight.Height; _bottomMiddle.X = _bottomLeft.Width; _bottomMiddle.Y = Height - _bottomMiddle.Height; _bottomMiddle.Width = Width - _bottomLeft.Width - _bottomRight.Width; //按鈕位置 if (MaximizeBox && MinimizeBox) // 允許最大化,最小化 { maxButton.Left = Width - maxButton.Width - maxButton.XOffset; minButton.Left = Width - minButton.Width - minButton.XOffset; selectSkinButton.Left = Width - selectSkinButton.Width - selectSkinButton.XOffset; } if (MaximizeBox && !MinimizeBox) //不允許最小化 { maxButton.Left = Width - maxButton.Width - maxButton.XOffset; selectSkinButton.Left = Width - selectSkinButton.Width - minButton.XOffset; minButton.Top = -60; } if (!MaximizeBox && MinimizeBox) //不允許最大化 { maxButton.Top = -60; minButton.Left = Width - maxButton.XOffset - minButton.Width; selectSkinButton.Left = Width - selectSkinButton.Width - minButton.XOffset; } if (!MaximizeBox && !MinimizeBox) //不允許最大化,最小化 { minButton.Top = -60; maxButton.Top = -60; selectSkinButton.Left = Width - selectSkinButton.Width - maxButton.XOffset; } if (!_showSelectSkinButton) { selectSkinButton.Top = -60; } closeButton.Left = Width - closeButton.Width - closeButton.XOffset; //內容panel位置大小 contentPanel.Top = _centerMiddle.Y; contentPanel.Left = _centerMiddle.X; contentPanel.Width = _centerMiddle.Width; contentPanel.Height = _centerMiddle.Height; }
ReadBitmap
這個方法是用來載入要使用皮膚的各個背景圖片的,大家看代碼就明白了,沒什麼好講的。
代碼 private void ReadBitmap(string skinFolder) { //讀取需要透明的顏色值 int r = int.Parse(IniHelper.ReadIniValue(skinFolder + "\\config.ini", "Main", "TransparentColorR")); int g = int.Parse(IniHelper.ReadIniValue(skinFolder + "\\config.ini", "Main", "TransparentColorG")); int b = int.Parse(IniHelper.ReadIniValue(skinFolder + "\\config.ini", "Main", "TransparentColorB")); Color trans = Color.FromArgb(r, g, b); TransparencyKey = trans; //透明處理 _topLeft.BackgroundBitmap = Image.FromFile(skinFolder + "\\TopLeft.bmp") as Bitmap; _topMiddle.BackgroundBitmap = Image.FromFile(skinFolder + "\\TopMiddle.bmp") as Bitmap; _topRight.BackgroundBitmap = Image.FromFile(skinFolder + "\\TopRight.bmp") as Bitmap; _centerLeft.BackgroundBitmap = Image.FromFile(skinFolder + "\\MiddleLeft.bmp") as Bitmap; _centerMiddle.BackgroundBitmap = Image.FromFile(skinFolder + "\\Middle.bmp") as Bitmap; _centerRight.BackgroundBitmap = Image.FromFile(skinFolder + "\\MiddleRight.bmp") as Bitmap; _bottomLeft.BackgroundBitmap = Image.FromFile(skinFolder + "\\BottomLeft.bmp") as Bitmap; _bottomMiddle.BackgroundBitmap = Image.FromFile(skinFolder + "\\BottomMiddle.bmp") as Bitmap; _bottomRight.BackgroundBitmap = Image.FromFile(skinFolder + "\\BottomRight.bmp") as Bitmap; minButton.ReadButtonImage(skinFolder + "\\MinNormal.bmp", skinFolder + "\\MinMove.bmp", skinFolder + "\\MinDown.bmp"); maxButton.ReadButtonImage(skinFolder + "\\MaxNormal.bmp", skinFolder + "\\MaxMove.bmp", skinFolder + "\\MaxDown.bmp"); closeButton.ReadButtonImage(skinFolder + "\\CloseNormal.bmp", skinFolder + "\\CloseMove.bmp", skinFolder + "\\CloseDown.bmp"); selectSkinButton.ReadButtonImage(skinFolder + "\\SelectSkinNormal.bmp", skinFolder + "\\SelectSkinMove.bmp", skinFolder + "\\SelectSkinDown.bmp"); }
DrawBackground
前面所有的東西都準備好了,現在就可以畫背景了。在哪裡調用?當然在OnPaint裡面。每一次表單變化都會調用這個函數。(不知道這種方式和直接拉個picturebox然後設定背景哪個好?這種直接畫的方式會不會因為onpaint的頻繁調用而受到影響?)
因為原來左上方的表徵圖被背景圖片遮住了,所以在這個方法中也就順便將表徵圖和標題畫上去了。
代碼 private void DrawBackground(Graphics g) { if (_topLeft.BackgroundBitmap == null) //確認已經讀取圖片 { return; } #region 繪製背景 ImageAttributes attribute = new ImageAttributes(); attribute.SetWrapMode(WrapMode.TileFlipXY); _topLeft.DrawSelf(g, null); _topMiddle.DrawSelf(g, attribute); _topRight.DrawSelf(g, null); _centerLeft.DrawSelf(g, attribute); contentPanel.BackgroundImage = _centerMiddle.BackgroundBitmap; //中間的背景色用內容panel背景代替 _centerRight.DrawSelf(g, attribute); _bottomLeft.DrawSelf(g, null); _bottomMiddle.DrawSelf(g, attribute); _bottomRight.DrawSelf(g, null); attribute.Dispose(); //釋放資源 #endregion #region 繪製標題和LOGO //繪製標題 if (!string.IsNullOrEmpty(Text)) { g.DrawString(Text, Font, new SolidBrush(ForeColor), ShowIcon ? new Point(_titlePoint.X + 18, _titlePoint.Y) : _titlePoint); } //繪製表徵圖 if (ShowIcon) { g.DrawIcon(Icon, new Rectangle(4, 4, 18, 18)); } #endregion }
說完了主要方法,下面看看提供的幾個屬性:
這裡想提的就是skinfolder這個屬性。按照理想的樣子這裡選擇的時候應該直接彈出已有皮膚的選項直接選擇。但是問題是我沒有找到在設計模式下讀取程式所在目錄的方法(設計模式下Application.StartupPath讀取到的是vs的目錄),所以只好採取這種方法讓設計者選擇皮膚目錄。在設計的時候程式到這個目錄下讀取配置資訊,實際啟動並執行時候程式自動截取skinfolder這個屬性中的皮膚名字,再通過application.startuppath讀取皮膚。
一些細節
1.該表單預設已經嵌入了一套皮膚(春色),所以即使您沒有皮膚檔案夾也能照樣顯示,只不過就一套皮膚罷了。
2.使用方法:項目中引用QLFUI.DLL,然後在要使用的類中將繼承類由Form類改為QLFUI.Mainfrm即可。
3.因為前面的系列已經有了表單詳細的實現,所以換膚這裡我只主要講了下換膚的部分。表單製作的細節就不再贅述了。
4.如果您有好的見解或者疑問歡迎和我探討,郵箱qianlf2008@163.com
源碼下載
下載