分享一个换肤解决方案

最近有朋友问到在winform程序上要做换肤功能的话,该如何处理,刚好前一段时间在项目中主导了程序换肤的这个功能.那就借这个机会整理一下,以分享给有需要的朋友.

1. 在winform程序上换肤,需要处理的涉及到每个控件及窗体.熟悉前端的朋友应该知道,在网页上实现换肤主要通过在每个元素上定义指定的标识符(如class,id等特性),然后页面通过加载不同的样式文件去渲染不同的皮肤效果,其实在winform程序中实现的思想应该是一致.

2.如上描述,我们可能需要定制使用到的每个控件,以便能读取指定的样式,并能根据样式渲染效果.

3.描述样式特征,我们可主要从以下几个方面考虑: 字体,颜色,图片,边框(当然延伸一下应该有对应的各种事件效果支持).

4.作为扩展,我们可能还希望样式可以在外面灵活的配置样式.

当然,市面上已经有很多成熟的winform皮肤组件产品,这类用于处理标准的后台管理类软件是已经很足够了,各位如果有类似需求也比较推荐这种形式.只不过我们的项目有些特殊(触摸屏),里面大部分的功能不能使用原生态的控件得以完成.如下面的展示效果.各位,看到这里,如果觉得不合胃口,请绕道,有兴趣的再往下看.

  1. 简单分析一下这个的实现.

    1. 在上面的简单分析中,我们首先需要定义一个用于描述控件最原始样式信息,这里姑且命名为ControlStyle,它最少应该包含用于筛选样式的作用基本的颜色,字体,鼠标切换样式等,同时我们看到一个控件(如panel)可能当放在不同的区域后,需要展示出不同的效果,基于此,我们还需要在这个原始ControlStyle中增加上用于描述样式区域选项RegionType.
    2. 我们将在自定控件中通过定义RegionType属性,并获取样式集合中与当前RegionType一致的样式作为当前控件需要渲染的效果.
    3. 基于以上简单分析,我们来简单画一个UML草图.
  2. 在上面的分析中,我们大致明白完成这个功能需要有一个承载控件展示效果的样式集合,以及各个控件根据自己对应的主题样式分别渲染.在这里,姑且我们将这里的样式管理者定义为ApplicationStyle, 它负责对外提供某个主题下各个控件样式的定义以及作为每个具体主题的基类.基于此,我们得到了类似如下的UML草图.                                     
  3. 基于以上的分析,简单看一下这个ApplicationStyle实现.

    /// <summary>
        /// 应用程序样式
        /// </summary>
        public abstract class ApplicationStyle
        {
            private static string _currentSkinName;       //当前主题名称
            private static ApplicationStyle _current;       //缓存当前样式
            private static object sync = new object();     //单例锁
    
            /// <summary>
            /// 默认主题名称
            /// </summary>
            protected static readonly string DefaultSkinName = "EnergyYellowApplicationStyle";
    
            /// <summary>
            /// 皮肤名称 Tag
            /// </summary>
            protected static string SkinNameTag { get; set; }
    
            /// <summary>
            /// 获取或设置当前主题名称
            /// </summary>
            public static string CurrentSkinName
            {
                get
                {
                    if (string.IsNullOrWhiteSpace(_currentSkinName))
                    {
                        _currentSkinName = DefaultSkinName;
                    }
                    return _currentSkinName;
                }
                set
                {
                    if (!string.IsNullOrWhiteSpace(value) && !string.Equals(value, _currentSkinName))
                    {
                        //如果为自定义皮肤
                        if (value.StartsWith("CustomApplicationStyle|", StringComparison.CurrentCultureIgnoreCase) && value.Length > "CustomApplicationStyle|".Length)
                        {
                            _currentSkinName = "CustomApplicationStyle";
                            SkinNameTag = value.Substring("CustomApplicationStyle|".Length);
    
                            //判断自定义文件是否存在
                            var cusSkinFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Skins", SkinNameTag, "Skin.skn");
                            if (!File.Exists(cusSkinFile))
                            {
                                _currentSkinName = DefaultSkinName;
                            }
                        }
                        else
                        {
                            _currentSkinName = value;
                        }
                        var temp = Current;             //临时加载皮肤
                    }
                }
            }
    
            /// <summary>
            /// 获取当前正在使用的样式主题
            /// </summary>
            public static ApplicationStyle Current
            {
                get
                {
                    if (_current == null)
                    {
                        lock (sync)
                        {
                            if (_current == null)
                            {
                                _current = LoadCurrentStyle();
                            }
                        }
                    }
                    return _current;
                }
            }
    
            /// <summary>
            /// 皮肤标题
            /// </summary>
            public abstract string SkinTitle { get; }
    
            /// <summary>
            /// 皮肤名称
            /// </summary>
            public abstract string SkinName { get; }
    
            /// <summary>
            /// 主题颜色
            /// </summary>
            public Color MainThemeColor { get; protected set; }
    
            /// <summary>
            /// Grid 样式集合
            /// </summary>
            public List<GridStyle> GridStyles { get; protected set; }
    
            /// <summary>
            /// Pop 弹出类型样式集合
            /// </summary>
            public List<PopPanelStyle> PopPanelStyles { get; protected set; }
    
            /// <summary>
            /// 按钮样式集合
            /// </summary>
            public List<ButtonStyle> ButtonStyles { get; protected set; }
    
            protected ApplicationStyle()
            {
    
            }
    
            /// <summary>
            /// 加载当前样式
            /// </summary>
            /// <returns></returns>
            private static ApplicationStyle LoadCurrentStyle()
            {
                ApplicationStyle temp = null;         
    
               //通过反射实例化当前正在使用的主题样式
                try
                {
                    var type = Type.GetType(string.Format("Skins.{0}", CurrentSkinName));
                    temp = Activator.CreateInstance(type) as ApplicationStyle;
                    temp.InitStyles();    //初始化样式
                }
                catch
                {
                    temp = new PeacockBlueApplicationStyle();
                    temp.InitStyles();
                }
    
                if (temp == null)
                {
                    temp = new PeacockBlueApplicationStyle();
                    temp.InitStyles();
                }
                return temp;
            }
    
            /// <summary>
            /// 初始化样式
            /// </summary>
            public virtual void InitStyles()
            {
                try
                {
                    InitOrderDishGridStyles();
                }
                catch (Exception ex)
                {
                    LogUtil.Error("初始化点菜界面已点列表样式失败", ex);
                }
    
                try
                { InitGridStyles(); }
                catch (Exception ex)
                {
                    LogUtil.Error("初始化Grid样式失败", ex);
                }
                try
                { InitButtonStyles(); }
                catch (Exception ex)
                {
                    LogUtil.Error("初始化Button样式失败", ex);
                }
            }
    
            #region 初始化样式集合   protected abstract void InitGridStyles();
            protected abstract void InitButtonStyles();
            protected abstract void InitPopPanelStyles();
            #endregion
        }
  4. 有了以上的基础,我们来尝试着改写一个控件的渲染效果,这里以DataGridView控件为例.

    /// <summary>
        /// ExDataGridView
        /// </summary>
        public class ExDataGridView : DataGridView
        {
            private BodyOrDialogRegionType _regionType = BodyOrDialogRegionType.None;
            private GridStyle _style;
    
            /// <summary>
            /// 应用主题样式
            /// </summary>
            private void ApplyStyle()
            {
                if (_regionType != RMS.Skins.ControlStyles.BodyOrDialogRegionType.None && _style != null)
                {
                    this.ColumnHeadersDefaultCellStyle.BackColor = _style.Header.BackColor;
                    this.ColumnHeadersDefaultCellStyle.ForeColor = _style.Header.ForeColor;
                    this.ColumnHeadersDefaultCellStyle.Font = _style.Header.Font;
                    this.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
                    this.BackgroundColor = _style.BackColor;
    
                    this.RowsDefaultCellStyle.BackColor = _style.Row.BackColor;
                    this.RowsDefaultCellStyle.ForeColor = _style.Row.ForeColor;
                    this.Font = _style.Row.Font;
    
                    this.RowsDefaultCellStyle.SelectionBackColor = _style.Row.SelectedBackColor;
                    this.RowsDefaultCellStyle.SelectionForeColor = _style.Row.SelectedForeColor;
                    this.RowsDefaultCellStyle.Font = _style.Row.Font;
                    this.RowTemplate.DefaultCellStyle.BackColor = _style.Row.BackColor;
                    this.RowTemplate.DefaultCellStyle.ForeColor = _style.Row.ForeColor;
                    this.RowTemplate.DefaultCellStyle.Font = _style.Row.Font;
                    this.BorderColor = _style.BorderColor;
                    this.GridColor = _style.GridColor;
    
                }
            }
    
            /// <summary>
            /// 设置或获取控件所处区域
            /// </summary>
            public BodyOrDialogRegionType RegionType
            {
                get { return _regionType; }
                set
                {
                    _regionType = value; 
    
                    //加载当前区域所对应的样式
                    _style = ApplicationStyle.Current.GridStyles.FirstOrDefault(t => t.RegionType == _regionType);
    
                    ApplyStyle();
                    this.Invalidate();
                }
            }
    
            /// <summary>
            /// 构造函数
            /// </summary>
            public POSDataGridView()
            {
                this.EnableHeadersVisualStyles = false;
                this.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.EnableResizing;
                this.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
                this.ColumnHeadersHeight = 37;
                this.ShowRowErrors = false;
                this.RowHeadersVisible = false;
    
                this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
            }
    
            /// <summary>
            /// 在展示布局的时候,重新应用样式
            /// </summary>
            /// <param name="e"></param>
            protected override void OnLayout(LayoutEventArgs e)
            {
                base.OnLayout(e);
                ApplyStyle();
            }
    
            /// <summary>
            /// 边框颜色
            /// </summary>
            public Color BorderColor { get; set; }
    
            /// <summary>
            /// 处理绘制事件
            /// </summary>
            /// <param name="e"></param>
            protected override void OnPaint(PaintEventArgs e)
            {
                base.OnPaint(e);
    
                //绘制边框
                using (var p = new Pen(BorderColor))
                {
                    e.Graphics.DrawRectangle(p, 0, 0, this.Width - 1, this.Height - 1);
                }
            }
    
            /// <summary>
            /// 处理单元格绘制事件,应用自定义样式效果
            /// </summary>
            /// <param name="e"></param>
            protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
            {
                base.OnCellPainting(e);
    
                //表头
                if (e.RowIndex == -1)
                {
                    DrawCellLine(e, this.GridColor, DashStyle.Solid);
                }
                else
                {
                    DrawCellLine(e, this.GridColor, DashStyle.Dot);
                }
            }
    
            /// <summary>
            /// 绘制表格边框
            /// </summary>
            /// <param name="e"></param>
            /// <param name="borderColor"></param>
            /// <param name="backgroundColor"></param>
            /// <param name="lineMode"></param>
            private void DrawCellLine(DataGridViewCellPaintingEventArgs e, Color borderColor, DashStyle lineStyle)
            {
                if (_style != null && _regionType != BodyOrDialogRegionType.None)
                {
                    var backgroundColor = _style.Header.BackColor;// this.ColumnHeadersDefaultCellStyle.BackColor;
                    if (e.RowIndex > -1)
                    {
                        backgroundColor = this.RowsDefaultCellStyle.BackColor;
                        if (this.Rows[e.RowIndex].Selected)
                        {
                            backgroundColor = this.RowsDefaultCellStyle.SelectionBackColor;
                        }
                    }
    
                    e.Graphics.FillRectangle(new SolidBrush(backgroundColor), e.CellBounds);
                    e.PaintContent(e.CellBounds);
    
                    var rect = e.CellBounds;
                    rect.Offset(new Point(-1, -1));
                    var pen = new Pen(new SolidBrush(borderColor));
                    pen.DashStyle = lineStyle;
                    e.Graphics.DrawLine(pen, rect.X, rect.Y + rect.Height, rect.X + rect.Width, rect.Y + rect.Height);
    
                    e.Handled = true;
                }
            }
  5. 最后,我们仅需要在程序开始运行的时候,设置当前配置主题样式名称即可.如:ApplicationStyle.CurrentSkinName = Configs.SkinName;

后记, 在程序中,换肤是一个比较常见的功能,也有很多成熟的实现方案,本处仅提供一种方案供大家参考. 另外,在我们的UML图里有一个自定义的主题CustomApplicationStyle对象,这个就不打算深入讨论了,无非就是从指定的配置中读取样式主题需要的东西来组合成系统期望的样式集合而已.

时间: 2024-10-29 19:08:12

分享一个换肤解决方案的相关文章

200行代码打造超越一线互联网公司的换肤架构

本专栏专注分享大型Bat面试知识,后续会持续更新,喜欢的话麻烦点击一个关注 面试官: 网易云QQ的换肤是怎么做到的,你对换肤有了解吗?看过换肤的原理没? 心理分析:没有接触过换肤技术 第一次听到该名词肯定会很茫然.面试官考的是对资源加载,监听布局,有没有了解.本文从换肤实战一对一讲解.告诉你如何做以及实现.文章末尾带换肤项目源码 求职者: 从监听布局开始到 换肤原理,详细给面试官讲解换肤的原理 接下来我们一起分享这篇干货. Android的主题换肤 ,可插件化提供皮肤包,无需Activity的重

android 换肤模式总结

由于Android的设置中并没有夜间模式的选项,对于喜欢睡前玩手机的用户,只能简单的调节手机屏幕亮度来改善体验.目前越来越多的应用开始把夜间模式加到自家应用中,没准不久google也会把这项功能添加到Android系统中吧. 业内关于夜间模式的实现,有两种主流方案,各有其利弊,我较为推崇第三种方案: 1.通过切换theme来实现夜间模式.2.通过修改uiMode来切换夜间模式. 3.通过插件方式切换夜间模式. 值得一提的是,上面提到的几种方案,都是资源内嵌在Apk中的方案,像新浪微博那种需要通过

Android主题换肤 无缝切换

2016年7月6日 更新:主题换肤库子项目地址:ThemeSkinning,让app集成换肤更加容易.欢迎star以及使用,提供改进意见. 更新日志: v1.3.0:增加一键切换切换字体(初版)v1.2.1:完善之前版本View的创建v1.2.0:增加对换肤属性自定义扩展v1.1.0:可以直接加载网络上的皮肤文件 今天再给大家带来一篇干货. Android的主题换肤 ,可插件化提供皮肤包,无需Activity的重启直接实现无缝切换,可高仿网易云音乐的主题换肤. 这个链接是本次的Demo打包出来的

插件式换肤框架搭建 - 资源加载源码分析

1. 概述 大部分控件我们都会使用,但是我们未必知道其资源加载的原理,目前换肤的框架比较多我们可以随随便便拿过来用,但早在几年前这些资料是比较少的,如果想做一个换肤的框架那就只能自己一点一点啃源码. 如果说我们现在不去用第三方的开源框架,要做一个换肤的功能,摆在我们面前的其实只有一个问题需要解决,那就是如何读取另外一个皮肤apk中的资源. 所有分享大纲:2017Android进阶之路与你同行 视频讲解地址:http://pan.baidu.com/s/1bC3lAQ 2. 资源加载源码分析 2.

换肤系统(oocss方式)

近期想做一个换肤系统,参考过Bootstrap系统,思前想后,内容不难,但就是理不清楚,主要是换肤系统的css如何设计,怎样设计可重用性最好,后期更方便修改和维护,还有一个最头疼的就是怎么给css进行命名,没有一个系统的,清晰的,一目了然的命名恐怕自己做到最后也都忘了.今天看到咱们博客上的一篇文章,受益良多,面向对象CSS(OOCSS),原谅我才疏学浅,2008年就被提出来了,我现在才第一次见这个名词.虽然还有人没听过这个名词,但其实也许自己已经用过这个概念和方式,它不是针对换肤系统被提出的概念

hybird之web动态换肤实现

前言 最近在重构个hybird(原生的壳包着Web页面)的UI框架,进行到了做换肤功能的阶段,所以这里是我思考的解决的方法. 预想 目前实现换肤的功能无非就两种做法. 1.写几个皮肤文件,然后切换使用这几个文件达到换肤的目的. 不得不说这是最常见的方式,效果也比较明显,但是它有几个缺点. 缺点: 1.如果更改一个皮肤的内容,那其他的皮肤文件也要做相应修改(这挺麻烦,不过可以用less管理css解决,所以也不是什么大问题). 2.它是固定的,在使用的时候皮肤文件已经是写好的了,而当我需要动态设置一

Android中插件开发篇之----应用换肤原理解析

一.前言 今天又到周末了,感觉时间过的很快呀.又要写blog了.那么今天就来看看应用的换肤原理解析.在之前的一篇博客中我说道了Android中的插件开发篇的基础:类加载器的相关知识.没看过的同学可以转战: http://blog.csdn.net/jiangwei0910410003/article/details/41384667 二.原理介绍 现在市场上有很多应用都有换肤的功能,就是能够提供给用户一些皮肤包,然后下载,替换.而且有些皮肤是要收费的.对于这个功能的话,其实没有什么技术难度的,但

解决duilib使用zip换肤卡顿的问题(附将资源集成到程序中的操作方法)

转载请说明原出处,谢谢~~ 今天在做单子是.客户要求做换肤功能,为此我专门写了一个换肤函数,而且把各种皮肤资源压缩为各个zip文件来换肤.可是客户反映程序执行缓慢,我測试后发现的确明显能够看出慢了不少.最后发现问题在于把皮肤资源都集成到了zip文件里,程序在刷新界面时会又一次从zip文件里读取相应的资源,导致了界面反映卡顿. 之前直接把z资源放到文件夹里或者把zip集成到程序内部,都是没问题的. 可是假设要换肤就须要用到zip来压缩资源了. duilib的WinImplBase类为我们提供了4种

qt之窗口换肤(一个qss的坑:当类属性发现变化时需要重置qss)

1.相关文章 Qt 资源系统qt的moc,uic,rcc命令的使用 2.概要    毕业两年了,一直使用的是qt界面库来开发程序,使用过vs08.10.13等开发工具,并安装了qt的插件,最近在做客户端换肤功能,所以就对qt的qrc做了点儿研究,我是一个实干派(可能有点儿虚),相对于看文档来说.本文开头我就给出了两篇博客,这两篇博客对我理解qrc这个东西有很大的帮助,接下来我就简单分析下我的理解. 首先说明下qrc是qt的东西,而不属于vs,这也很容易证明,那就是vs的工程师不识别qrc文件的,