分享一个换肤解决方案

摘要:
最近,一位朋友问如何处理winform程序上的皮肤变化功能。就在不久前,我在项目中主导了程序的皮肤变化功能。让我们借此机会整理一下,并与有需要的朋友分享。1.winform程序的外观更改涉及需要处理的每个控件和表单。熟悉前端的朋友应该知道,在网页上实现皮肤变化的主要方法是在每个元素上定义指定的标识符(例如类、id和其他特征),然后页面可以通过加载不同的样式文件来呈现不同的皮肤效果。事实上,在winfor

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

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

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

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

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

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

image

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

免责声明:文章转载自《分享一个换肤解决方案》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇svg 直线水平渐变为什么没有效果,必须得是一条倾斜的不水平的直线才有渐变效果呢??12.bss段的初始化下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Linux套接字与虚拟文件系统(1):初始化和创建

http://www.cppblog.com/qinqing1984/archive/2015/05/03/210521.html 引言在Unix的世界里,万物皆文件,通过虚拟文件系统VFS,程序可以用标准的Unix系统调用对不同的文件系统,甚至不同介质上的文件系统进行读写操作。对于网络套接字socket也是如此,除了专属的Berkeley Sockets...

golang-指针,函数,map

指针普通类型变量存的就是值,也叫值类型。指针类型存的是地址,即指针的值是一个变量的地址。一个指针只是值所保存的位置,不是所有的值都有地址,但是所有的变量都有。使用指针可以在无需知道变量名字的情况下,间接读取或更新变量的值。 获取变量的地址,用&,例如:var a int 获取a的地址:&a,&a(a的地址)这个表达式获取一个指向整型...

Go-结构体

Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。 类型别名和自定义类型 自定义类型 在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型,Go语言中可以使用type关键字来定义自定义类型。 自定义类型是定义了一个全新的类型。我们可以基于内置的...

VC++函数只被调用一次

如何保证某个函数只被调用一次   一个函数caller会在其内部调用另外一个函数callee,现在的情况是,caller可能会在多个地方被多次调用,而你希望callee只在第一次被调用时被调用一次。一般情况下,callee会是一个对环境或者资源的初始化工作。 或许,从代码结构的角度来讲,你第一个想到的是把callee从caller中拿出来,放到某个合适的...

经典MFC界面和Ribbon界面框架对比(单文档为例)

IDE为:VS2008+SP1 A为经典MFC单文档界面: B为Ribbon风格的MFC单文档界面: 在生成A和B的基础框架代码之后,通过对比工具,发现这两种不同风格的界面主要在MainFrame类中存在一些差异。 应用程序类(CXXXApp)的InitInstance()函数中,B风格在调用InitContextMenuManager()之后,执行了...

ARMGNU伪指令

符号定义伪指令 .global,.local,.set,.equ .global 使得符号对连接器可见,变为对整个工程可用的全局变量 .global symbol .local 表示符号对外部不可见,只对本文件可见 .local symbol .set 给一个全局变量或局部变量赋值,和.equ的功能一样 .set symbol expr .set s...