[C#.NET][Winform][User Control] 使用 IExtenderProvider 擴充 控制項樣式模版

[C#.NET][Winform][User Control] 使用 IExtenderProvider 擴充 控制項樣式模版

一直以來都想要用一種方式來快速換掉整個 Form/Control 的Skin以及Style,Devexpress元件就有這樣的好功能,真的很威,這元件要價$800 USD多,今天不是要介紹該元件。

image

我這次想要,在不重寫控制項的情況下使用 IExtenderProvider ,來為每一個.NET預設的控制項新增一個屬性,讓那個屬性決定控制項的樣式,我腦中這樣想著,所以

我需要一個StyleContainer元件,為控制項,新增一個Style屬性,

image

由StyleAppearance元件則決定樣式,比如說字型、顏色、滑鼠事件樣式,一個StyleAppearance可以套用在不同的控制項

image

我們也可以為控制項在不同的狀態下展現不同的樣式,比如說Focus、Disable,你若想要學習如何使用就接著看下去吧。


@Appearance.cs,定義Appearance 類別,這裡存放樣式定義


using System.Drawing;
 
namespace System.Windows.Forms.Extend.Style
{
    [TypeConverter(typeof(ExpandableObjectConverter))]
    public class Appearance : INotifyPropertyChanged
    {
        public Appearance() { }
        public const string BindingFont = "Font";
        public const string BindingBackColor = "BackColor";
        public const string BindingForeColor = "ForeColor";
 
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
 
        private Font _Font = new Font("Microsoft Sans Serif", 8.25f);
        [DefaultValue(typeof(Font), "Microsoft Sans Serif")]
        public Font Font
        {
            get { return _Font; }
            set
            {
                _Font = value;
                OnPropertyChanged(BindingFont);
            }
        }
 
        private Color _BackColor = SystemColors.Control;
        [DefaultValue(typeof(Color), "Control")]
        public Color BackColor
        {
            get { return _BackColor; }
            set
            {
                _BackColor = value;
                OnPropertyChanged(BindingBackColor);
            }
        }
 
        private Color _ForeColor = SystemColors.ControlText;
        [DefaultValue(typeof(Color), "ControlText")]
        public Color ForeColor
        {
            get { return _ForeColor; }
            set
            {
                _ForeColor = value;
                OnPropertyChanged(BindingForeColor);
            }
        }
 
        public override string ToString()
        {
            return "Appearance";
        }
    }
}
@StyleAppearance.cs 用在VS設計階段的元件,以便在VS裡選擇套用

namespace System.Windows.Forms.Extend.Style
{
    public partial class StyleAppearance : Component
    {
        public StyleAppearance()
        {
            InitializeComponent();
        }
 
        public StyleAppearance(IContainer container)
            : this()
        {
            container.Add(this);
        }
 
        private Appearance _AppearanceNone = new Appearance();
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public Appearance AppearanceNone
        {
            get
            {
                return _AppearanceNone;
            }
            set
            {
                _AppearanceNone = value;
            }
        }
    }
}
@StyleContainer.cs 為控制項增加屬性

using System.ComponentModel;
 
namespace System.Windows.Forms.Extend.Style
{
    [ProvideProperty("Style", typeof(Control))]
    public partial class StyleContainer : Component, IExtenderProvider
    {
        #region 實作 IExtenderProvider 成员
        public bool CanExtend(object target)
        {
            if (target is Form)
                return false;
            else
                return true;
        }
 
        #endregion
 
        public StyleContainer()
        {
            InitializeComponent();
        }
 
        public StyleContainer(IContainer container)
        {
            container.Add(this);
 
            InitializeComponent();
        }
 
        private Dictionary<Control, StyleAppearance> _Styles = new Dictionary<Control, StyleAppearance>();
 
        public void SetStyle(Control Ctrl, StyleAppearance Style)
        {
            if (this._Styles.ContainsKey(Ctrl))
            {
                this._Styles[Ctrl] = Style;
            }
            else
            {
                this._Styles.Add(Ctrl, Style);
            }
            if (Style == null)
            {
                Ctrl.Font = null;//沒有選擇StyleAppearance時則清掉字型,在VS設計階段時可觀察用
            }
            else
            {
                Ctrl.Font = Style.AppearanceNone.Font;//有選擇StyleAppearance時則指定字型,在VS設計階段時可觀察用
                Style.AppearanceNone.PropertyChanged += new PropertyChangedEventHandler(Appearance_PropertyChanged);
            }
        }
 
 
        void Appearance_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Appearance appearance = (Appearance)sender;
            //TODO:可以利用反射再處理過
            foreach (var item in this._Styles)
            {
                Control ctrl = item.Key;
                StyleAppearance style = item.Value;
 
                if (ctrl == null || style == null)
                    continue;
                switch (e.PropertyName)
                {
                    case Appearance.BindingFont:
                        ctrl.Font = appearance.Font;
                        break;
                    case Appearance.BindingBackColor:
                        ctrl.BackColor = appearance.BackColor;
                        break;
                    case Appearance.BindingForeColor:
                        ctrl.ForeColor = appearance.ForeColor;
                        break;
                }
            }
        }
 
        [DefaultValue(null)]
        public StyleAppearance GetStyle(Control Ctrl)
        {
            if (this._Styles.ContainsKey(Ctrl))
            {
                return this._Styles[Ctrl];
            }
            else
            {
                return null;
            }
        }
 
        public override string ToString()
        {
            return "StyleContainer";
        }
    }
}
這樣一來便完成了基本的樣式套版


完成後

SNAGHTMLd65c18

選擇已建立的StyleAppearance元件

image

在StyleAppearance元件裡修改AppearanceNone屬性的字型大小

SNAGHTMLd71577

看看VS幫我們產生的程式碼

SNAGHTMLda5322

在StyleContainer.cs我們處理這個事件Style.AppearanceNone.PropertyChanged += new PropertyChangedEventHandler(Appearance_PropertyChanged); 所以在執行階段能幫我們處理控制項屬性
若要在VS設計階段觀察我們變更的值,我們則在SetStyle裡處理
image
到目前為止我們已經完成了基本的功能樣式變更,如果要讓控制項停用時有不一樣的樣式呢?該怎麼做呢?用程式碼變更控制項的Enable屬性後,怎麼捕捉Enable屬性變更事件?
這又是另一項工程,留到下回再戰吧~

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo