软件易容术-----换肤
                                                      电子科技大学03级02班 周银辉

1, 看看成果:

换肤前

换肤后

(说明: 这里仅仅借用的是"千千静听"中皮肤包中的图片, 本示例程序中的皮肤包文件格式以及换肤方案均为作者原创)

2  如何实现:

2.1 皮肤配置文件:
在研究如何实现换肤之前,应该仔细看看以下XML文件,它是皮肤配置文件,由它来组织皮肤包中的图片并告诉软件如何来绘制皮肤:

皮肤配置文件(点击加号展开)
<?xml version="1.0" encoding="utf-8"?>
<PlayerSkin xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <MainWindow>
    <Image>player_skin.bmp</Image>
    <Icon>
      <Position>
        <Left>1</Left>
        <Top>1</Top>
        <Right>17</Right>
        <Bottom>17</Bottom>
      </Position>
      <IconImage>icon.ico</IconImage>
    </Icon>
    <Play>
      <Position>
        <Left>164</Left>
        <Top>39</Top>
        <Right>183</Right>
        <Bottom>58</Bottom>
      </Position>
      <Iamge>play.bmp</Iamge>
    </Play>
    <Pause>
      <Position>
        <Left>164</Left>
        <Top>39</Top>
        <Right>183</Right>
        <Bottom>58</Bottom>
      </Position>
      <Iamge>pause.bmp</Iamge>
    </Pause>
    <Stop>
      <Position>
        <Left>145</Left>
        <Top>39</Top>
        <Right>164</Right>
        <Bottom>58</Bottom>
      </Position>
      <Iamge>stop.bmp</Iamge>
    </Stop>
    <Next>
      <Position>
        <Left>202</Left>
        <Top>39</Top>
        <Right>221</Right>
        <Bottom>58</Bottom>
      </Position>
      <Iamge>next.bmp</Iamge>
    </Next>
    <Previous>
      <Position>
        <Left>183</Left>
        <Top>39</Top>
        <Right>202</Right>
        <Bottom>58</Bottom>
      </Position>
      <Iamge>prev.bmp</Iamge>
    </Previous>
    <Mute>
      <Position>
        <Left>249</Left>
        <Top>39</Top>
        <Right>268</Right>
        <Bottom>58</Bottom>
      </Position>
      <Iamge>mute.bmp</Iamge>
    </Mute>
    <Open>
      <Position>
        <Left>221</Left>
        <Top>39</Top>
        <Right>240</Right>
        <Bottom>58</Bottom>
      </Position>
      <Iamge>open.bmp</Iamge>
    </Open>
    <Eq>
      <Position>
        <Left>152</Left>
        <Top>92</Top>
        <Right>188</Right>
        <Bottom>114</Bottom>
      </Position>
      <Iamge>equalizer.bmp</Iamge>
    </Eq>
    <PalyList>
      <Position>
        <Left>188</Left>
        <Top>92</Top>
        <Right>224</Right>
        <Bottom>114</Bottom>
      </Position>
      <Iamge>playlist.bmp</Iamge>
    </PalyList>
    <Lyric>
      <Position>
        <Left>224</Left>
        <Top>92</Top>
        <Right>260</Right>
        <Bottom>114</Bottom>
      </Position>
      <Iamge>lyric.bmp</Iamge>
    </Lyric>
    <Visual>
      <Position>
        <Left>18</Left>
        <Top>24</Top>
        <Right>124</Right>
        <Bottom>84</Bottom>
      </Position>
    </Visual>
    <MiniMize>
      <Position>
        <Left>240</Left>
        <Top>3</Top>
        <Right>247</Right>
        <Bottom>10</Bottom>
      </Position>
      <Iamge>minimize.bmp</Iamge>
    </MiniMize>
    <MiniMode>
      <Position>
        <Left>253</Left>
        <Top>3</Top>
        <Right>260</Right>
        <Bottom>10</Bottom>
      </Position>
      <Iamge>minimode.bmp</Iamge>
    </MiniMode>
    <Exit>
      <Position>
        <Left>266</Left>
        <Top>3</Top>
        <Right>273</Right>
        <Bottom>10</Bottom>
      </Position>
      <Iamge>close.bmp</Iamge>
    </Exit>
    <Progress>
      <Position>
        <Left>145</Left>
        <Top>77</Top>
        <Right>267</Right>
        <Bottom>90</Bottom>
      </Position>
      <BarImage></BarImage>
      <ThumbImage>progress_thumb.bmp</ThumbImage>
      <FillImage>progress_fill.bmp</FillImage>
    </Progress>
    <Volume>
      <Position>
        <Left>274</Left>
        <Top>20</Top>
        <Right>283</Right>
        <Bottom>84</Bottom>
      </Position>
      <BarImage></BarImage>
      <ThumbImage></ThumbImage>
      <FillImage>volume_fill.bmp</FillImage>
      <Vertical>true</Vertical>
    </Volume>
    <Info>
      <Position>
        <Left>147</Left>
        <Top>20</Top>
        <Right>268</Right>
        <Bottom>31</Bottom>
      </Position>
      <Color>#ff9933</Color>
      <Font>simsong</Font>
      <FontSize>9</FontSize>
      <Align>MiddleLeft</Align>
    </Info>
    <Led>
      <Position>
        <Left>175</Left>
        <Top>63</Top>
        <Right>235</Right>
        <Bottom>74</Bottom>
      </Position>
      <Iamge>number.bmp</Iamge>
      <Align>MiddleRight</Align>
    </Led>
    <Stereo>
      <Position>
        <Left>13</Left>
        <Top>98</Top>
        <Right>55</Right>
        <Bottom>109</Bottom>
      </Position>
      <Color>#ff9933</Color>
      <Font>simsong</Font>
      <FontSize>9</FontSize>
      <Align>MiddleLeft</Align>
    </Stereo>
    <Status>
      <Position>
        <Left>59</Left>
        <Top>98</Top>
        <Right>129</Right>
        <Bottom>109</Bottom>
      </Position>
      <Color>#ff9933</Color>
      <Font>simsong</Font>
      <FontSize>9</FontSize>
      <Align>MiddleLeft</Align>
    </Status>
  </MainWindow>
  <Name>Orange</Name>
  <TransparentColor>#ff00ff</TransparentColor>
</PlayerSkin>

很明显地,该配置文件中规定了皮肤中的各个元素的位置大小等相关信息. 比如:

   <Play>
      <Position>
        <Left>164</Left>
        <Top>39</Top>
        <Right>183</Right>
        <Bottom>58</Bottom>
      </Position>
      <Iamge>play.bmp</Iamge>
    </Play>

规定的"播放"按钮在主窗口中的位置以及其对应的图片.

很简单地,我们只要解析该XML文件并将其对应的图片绘制在指定的位置就可以了(当然此时还只是视觉上的,因为按钮还有事件等,稍后讨论)

2.2 如何组织窗口上的元素

"窗口上的元素"指的是窗口上的"按钮","滚动条"等. 注意:它们不是"Button" "ScrollBar"等控件,它们只是在指定位置上的图片,并响应相应事件)
为更好地组织各个元素,我们设计了一个"皮肤元素接口",让所有元素都实现该接口:

public interface ISkinElement
    {
        string Name
        {
            get;
        }

        Position Position
        {
            get;
            set;
        }

        ElementStatus Status
        {
            get;
            set;
        }

        void Paint(Graphics g);

        event SkinElementStatusChangedHandler SkinElementStatusChanged;
    }

其中
Name表示该元素的名称.

Position规定了该元素的大小和位置,它是这样一个结构:

    /**//// <summary>
    /// 位置信息,由左上角X、Y坐标和右下角X、Y坐标组成
    /// </summary>
    public struct Position
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;

        public Position(int left, int top, int right, int bottom)
        {
            this.Left = left;
            this.Top = top;
            this.Right = right;
            this.Bottom = bottom;
        }
    }

Status表示该元素的当前状态,它是这样一个枚举:

    /**//// <summary>
    /// 元素状态
    /// </summary>
    public enum ElementStatus
    {
        Normal,
        MouseHover,
        MouseDown,
        Disabled
    }

void Paint(Graphics g)是该元素的绘制函数
event SkinElementStatusChangedHandler SkinElementStatusChanged;是该元素状态发生改变时所引发的事件.

然后,比如"播放"按钮,就可以是如下的一个类:

播放按捏(点击加号展开)
    /**//// <summary>
    /// 播放元素(播放按钮)
    /// </summary>
    public class PlayElement : ISkinElement
    {
        private Position position;
        private string image = string.Empty;

        [NonSerialized]
        private ElementStatus status;

        SkinElement 成员#region SkinElement 成员

        /**//// <summary>
        /// 获取该元素的名称
        /// </summary>
        public string Name
        {
            get
            {
                return "PlayElement";
            }
        }

        /**//// <summary>
        /// 获取或设置该元素的位置
        /// </summary>
        public Position Position
        {
            get
            {
                return this.position;
            }
            set
            {
                this.position = value;
            }
        }

        /**//// <summary>
        /// 绘制该元素
        /// </summary>
        public void Paint(Graphics g)
        {
            SkinPainter.PaintSkinElement(g, this.position, this.image, this.status);
        }

        /**//// <summary>
        /// 获取或设置该对象的状态
        /// </summary>
        public ElementStatus Status
        {
            get
            {
                return this.status;
            }
            set
            {
                if (this.status != value)
                {
                    this.status = value;
                    this.OnSkinElementStatusChanged(new SkinElementStatusChangedArges(value));
                }
            }
        }

        /**//// <summary>
        /// 当元素状态改变时发生
        /// </summary>
        public event SkinElementStatusChangedHandler SkinElementStatusChanged;

        #endregion

        protected void OnSkinElementStatusChanged(SkinElementStatusChangedArges arg)
        {
            if (this.SkinElementStatusChanged != null)
            {
                this.SkinElementStatusChanged(this, arg);
            }
        }

        /**//// <summary>
        /// 获取或设置该对象对应的图片文件(相对于皮肤文件夹的相对路径)
        /// </summary>
        public string Iamge
        {
            get
            {
                return this.image;
            }
            set
            {
                this.image = value;
            }
        }

      
    }

其它元素同理.

2.3 如何绘制元素

以按钮为例:
一张按钮图片由四部分组成 , 它分别代表按钮的不同状态(Normal,MouseEnter,MouseDown, Disable), 我们只需要根据按钮的当前状态切取图片的不同部分并绘制在指定位置上便可.比如:

        /**//// <summary>
        /// 绘制普通的类似于按钮的皮肤元素。
         /// 进度条、音量控制等不应该采用此函数
         /// </summary>
        /// <param name="g">用其进行绘制</param>
        /// <param name="pos">绘制的位置</param>
        /// <param name="imgPath">绘制的图片的路径(相对路径,在此之前应该确定SkinRootDir属性已经被设置)</param>
        /// <param name="status">皮肤元素的当前状态</param>
        public static void PaintSkinElement(Graphics g, Position pos, string imgPath, ElementStatus status)
        {
            ImageAttributes imgAttributes = new ImageAttributes();
            imgAttributes.SetColorKey(transparentKey, transparentKey);

            try
            {
                Image img = Image.FromFile(SkinRootDir + System.IO.Path.DirectorySeparatorChar + imgPath);
                Rectangle destRect = new Rectangle(pos.Left, pos.Top, pos.Right - pos.Left, pos.Bottom - pos.Top);

                int x = 0;
                int y = 0;
                int width = img.Width / 4;
                int height = img.Height;

                switch (status)
                {
                    case ElementStatus.Normal:
                        x = 0;
                        break;
                    case ElementStatus.MouseHover:
                        x = width;
                        break;
                    case ElementStatus.MouseDown:
                        x = 2 * width;
                        break;
                    case ElementStatus.Disabled:
                        x = 3 * width;
                        break;
                    default:
                        break;
                }

                g.DrawImage(img, destRect, x, y, width, height, GraphicsUnit.Pixel, imgAttributes);
            }
            catch
            {
            }
        }

2.4 如何响应键盘鼠标等事件

以鼠标事件为例:
其实皮肤上的元素并没有这些事件,我们只是当用户点击主窗口时,根据鼠标点击的位置来确定点击在了哪个元素指上,并引发该元素所对应的事件.
查找鼠标点击的元素:

     /**//// <summary>
        /// 确定在指定的皮肤元素集合中,指定的点包含在哪个元素中.
        /// 如果同时包含在多个元素中,则以最内层的那个为准.
        /// <para>注意:由于元素之间没有Z轴层次关系,所以不应该让两个元素处在相交却不包含的关系中</para>
        /// </summary>
        /// <param name="elementList">元素集合,在它们中间查找</param>
        /// <param name="loc">要进行判断的点</param>
        /// <returns>如果不包含在指定的任一元素中,则返回null,否则返回包含该点的元素</returns>
        public static ISkinElement FindSkinElementFormPosition(List<ISkinElement> elementList, Point loc)
        {
            Rectangle rect = new Rectangle();
            Rectangle lastRect = Rectangle.Empty;
            ISkinElement res = null;
            foreach (ISkinElement element in elementList)
            {
                rect.X = element.Position.Left;
                rect.Y = element.Position.Top;
                rect.Width = element.Position.Right - element.Position.Left;
                rect.Height = element.Position.Bottom - element.Position.Top;
                if (rect.Contains(loc))
                {
                    if (lastRect == Rectangle.Empty || lastRect.Contains(rect))
                    {
                        lastRect = rect;
                        res = element;
                    }
                }
            }

            return res;
        }

比如,我们鼠标点击落在"关闭"元素内时,将关闭窗口:

 private void FormMain_MouseClick(object sender, MouseEventArgs e)
        {
            ISkinElement element =
              Helper.FindSkinElementFormPosition(this.skinMainFormElementList, e.Location);

            if (element != null)
            {
                switch (element.Name)
                {
                    case "ExitElement":
                        this.Close();
                        break;
                    default:
                        break;
                }
            }
        }

2.5 局部区域更新

比如鼠标移动到"播放"按钮上时,其状态将转换为"MouseEnter",将重新绘制该按钮以响应鼠标.此时只需要更新该按钮所对应的区域便可:

 private void FormMain_MouseMove(object sender, MouseEventArgs e)
 {
     ISkinElement element =
             Helper.FindSkinElementFormPosition(this.skinMainFormElementList, e.Location);
     if (element != null &&
                element.Status != ElementStatus.MouseHover &&
                element.Status != ElementStatus.Disabled)
        {
            element.Status = ElementStatus.MouseHover;
            this.UpdateSkinElement(element);
        }
}

其中UpdateSkinElement(ISkinElement element):

        /**//// <summary>
        /// 更新指定皮肤元素(重新绘制皮肤的指定区域)
        /// </summary>
        /// <param name="element">要被重绘的元素</param>
        private void UpdateSkinElement(ISkinElement element)
        {
            Rectangle updateRect = new Rectangle(
                    element.Position.Left,
                    element.Position.Top,
                    element.Position.Right - element.Position.Left,
                    element.Position.Bottom - element.Position.Top);

            this.Invalidate(updateRect);
            this.Update();
        }

3 DEMO下载:
http://files.cnblogs.com/zhouyinhui/Pastime.rar

转载于:https://www.cnblogs.com/zhouyinhui/archive/2006/10/30/544132.html

软件易容术-----换肤相关推荐

  1. 【Qt样式(qss)-2】使用小结(软件换肤,比如暗黑模式)

    1.背景: Qt style sheet(qss)跟前端技术一样,就是为了美化界面.关键是,太好用了.之前还为此写过一篇博客. [Qt样式(qss)-1]手册小结 [Qt样式(qss)-3]几套配色方 ...

  2. 用QT开发软件怎么实现一键换肤

    QT开发软件实现一键换肤可以使用QSS样式表和QResource动态加载资源. 创建不同的QSS样式表文件,定义不同的样式. 使用QResource动态加载样式表文件. 在程序中设置当前使用的样式表. ...

  3. Android应用换肤

    换肤,我们都很熟悉,像XP的主题,塞班的主题.看过国外的一些技术博客,就会发现国内和国外对软件的,或者说移动开发的软件的需求的不同.国外用户注重社交.邮件等功能,国内用户则重视音乐.小说.皮肤等功能, ...

  4. Qt之界面换肤的两种方式

    概述 像现在市面上有很多软件都有换肤功能,例如:QQ.360.迅雷等.换肤其实很简单,并没有想象中那么难,下面介绍两种实现换肤的方式,仅供参考! 方式一 1. 实现原理 创建多个QSS文件 首先,根据 ...

  5. 很好的 .NET 换肤软件 IrisSkin

    当前.NET下提供换肤的控件有IrisSkin和DotNetSkin. 但是DotNetSkin提供的Demo版本,功能有限制,一时找不到可以破解的完全版. IrisSkin 的功能不比DotNetS ...

  6. 代码换肤术——C#和VB(摘抄)

    代码换肤术(一)--C#和VB  日期:2005-6-13 16:41:00   代码换肤术(一)--C#和VB 现在流行程序"换肤术",就是把操作界面变个样子,程序当然还是原来的 ...

  7. SkinSharp(Skin#)软件换肤库!

    SkinSharp又称Skin#,是Windows环境下一款强大的换肤组件. SkinSharp作为换肤控件,只需要在您的程序中添加一行代码,就能让您的界面焕然一新,并拥有多种主题风格和色调的动态切换 ...

  8. ActiveSkin 4.3 软件换肤

    ActiveSkin是一款给软件更换皮肤的ActiveX控件.它很还好的将软件界面设计工作从繁琐程序代码编写中解放出来,使得功能设计者可以专心于功能代码的实现,而把软件界面交给美工人员处理.提高了界面 ...

  9. .NET 换肤软件 IrisSkin 2.0 破解版下载及多款皮肤下载

    当前.NET下提供换肤的控件有IrisSkin和DotNetSkin. 但是DotNetSkin提供的Demo版本,功能有限制,一时找不到可以破解的完全版. IrisSkin 的功能不比DotNetS ...

最新文章

  1. 读书:有趣 -- 酒鬼与圣徒
  2. Openstack组件部署 — Keystone功能介绍与认证实现流程
  3. c语言中宏替换时的顺序
  4. 搭建samba文件共享服务
  5. 进程控制块PCB结构体 task_struct 描述
  6. 是个狠角色。。 | 今日最佳
  7. Manjaro下显卡相关的命令搜集
  8. linux vi路径配置,Linux 下 Vi 配置文件 .vimrc 文件
  9. CICD详解(十一)——sonar详解
  10. Maven cmd 打包命令
  11. 【Java8】Function 讲解
  12. linux查看网卡光强,LINUX OV5640驱动
  13. Python的数据类型
  14. c++基础题:判断某整数是正整数、负整数还是零
  15. 焱融全闪存储轻松构建百亿私募量化投研平台
  16. Yii框架里的一些zii用法
  17. KubeCon 2021中国大会
  18. AAA和RADIUS
  19. 数据库基础知识(八)
  20. 各个排序算法的时间复杂度、稳定性、快排的原理以及图解

热门文章

  1. Kotlin入门(28)Application单例化
  2. django2.0 自己入门记录一些基础url 模板等
  3. 微信公众号迁移具体内容
  4. 温故js系列(4)-运算符详解
  5. Emacs-24.2 中很重要的几个函数--实现自定义语法高亮的关键
  6. 手机将与瘦客户机争夺办公桌面
  7. ubuntu9.04更新之后无法启动系统的解决办法
  8. 2018清华计算机类专业录取分数线,清华大学2018-2019年各省各专业录取分数线
  9. Zookeeper-01-概述
  10. Java-修改class文件