
這個系列好久都沒有更新了,貌似上一篇還是在今年五月份發布的。呵呵,不感慨了,還是開始介紹今天的內容吧! 這里說明一下這次實現的換膚都是基于貼圖換膚的,并不可以像QQ那樣還可以調整色調甚至自定義圖片為背景。如果您已經有過這方面的經驗,下面的內容或許不一定適合你。
這個系列好久都沒有更新了,貌似上一篇還是在今年五月份發布的。呵呵,不感慨了,還是開始介紹今天的內容吧! 這里說明一下這次實現的換膚都是基于貼圖換膚的,并不可以像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
下載