c#皮膚美化:窗體換膚方案

這次實現的換膚都是基於貼圖換膚的,並不可以像QQ那樣還可以調整色調甚至自定義圖片爲背景。如果您已經有過這方面的經驗,下面的內容或許不一定適合你。

      貼圖換膚就是用不同的圖片去畫不同的地方的背景,最後形成了界面的一個整體樣式外觀。只要我們將每個背景圖片的位置以及大小信息記錄下來,並在換膚的時候加載這些圖片和信息並將它們畫到背景上去就能實現換膚了。很簡單吧~~

      最終的效果圖:

查看 skin

換膚實現:

    上面只是簡單說了一下換膚的“原理”,下面這個換膚流程圖或許能夠幫助您更好理解它:

5 

上面的這四個過程就對應了實際類中的四個主要方法:ReadIniFile,CaculatePartLocation,ReadBitmap和DrawBackground。我們一個一個來看:

ReadIniFile

    這個方法主要用來讀取皮膚配置信息。 什麼?不知道配置信息長啥樣?得了,那就給你看一眼吧。

6

  這是個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
        }

說完了主要方法,下面看看提供的幾個屬性:

7

這裏想提的就是skinfolder這個屬性。按照理想的樣子這裏選擇的時候應該直接彈出已有皮膚的選項直接選擇。但是問題是我沒有找到在設計模式下讀取程序所在目錄的方法(設計模式下Application.StartupPath讀取到的是vs的目錄),所以只好採取這種方法讓設計者選擇皮膚目錄。在設計的時候程序到這個目錄下讀取配置信息,實際運行的時候程序自動截取skinfolder這個屬性中的皮膚名字,再通過application.startuppath讀取皮膚。

一些細節

      1.該窗體默認已經嵌入了一套皮膚(春色),所以即使您沒有皮膚文件夾也能照樣顯示,只不過就一套皮膚罷了。

      2.使用方法:項目中引用QLFUI.DLL,然後在要使用的類中將繼承類由Form類改爲QLFUI.Mainfrm即可。

      3.因爲前面的系列已經有了窗體詳細的實現,所以換膚這裏我只主要講了下換膚的部分。窗體制作的細節就不再贅述了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章