Search code examples
c#.netwinformswindows-forms-designeruitypeeditor

How can I override ColorUI class of ColorEditor class and add a new Tab with custom colors?


What I am trying to do is to add a custom tab with custom colors into the ColorUI class of ColorEditor class which appears into the PropertyGrid of controls.

Something like this: ( Just a fake image made in Photoshop ).

enter image description here

I understand that in some way I have to create my own ColorEditor class and override the ColorUI class which is nested inside original ColorEditor class but I don't know which Method I have to override so to add this extra Tab etc.

Any idea?


Solution

  • Probably a more flexible (and less hacky) solution will be recreating the ColorEditor as mentioned by Jimi. I also may take a totally different approach for supporting theme colors, for example creating a theme extender provider component, or creating derived controls or other possible solutions.

    Anyways, to support my previous post, specially where I mentioned 'You even can add another tab to the editor.', here I post a sample code to add another tab to the color editor, then you can see the custom tab (theme) with the custom colors that you need, like this:

    enter image description here

    It's a real screenshot ;) not exactly what you asked, but good enough as a sample code. For example if you need to use custom names for the colors, then you need more customization in DrawItem (like what I did in the example), or rewriting the control from scratch.

    And here's the code:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Drawing.Design;
    using System.Linq;
    using System.Reflection;
    using System.Windows.Forms;
    public class CustomColorEditor : ColorEditor
    {
        public override object EditValue(ITypeDescriptorContext
            context, System.IServiceProvider provider, object value)
        {
            //Get required types and methods
            var ColorUIType = typeof(ColorEditor).GetNestedType("ColorUI",
                BindingFlags.NonPublic | BindingFlags.Instance);
            var ColorUiConstructor = ColorUIType.GetConstructors()[0];
            var ColorEditorListBoxType = ColorUIType.GetNestedType("ColorEditorListBox",
                BindingFlags.NonPublic | BindingFlags.Instance);
            var ColorUiField = typeof(ColorEditor).GetField("colorUI",
                BindingFlags.NonPublic | BindingFlags.Instance);
            var OnListClickMethod = ColorUIType.GetMethod("OnListClick",
                BindingFlags.NonPublic | BindingFlags.Instance);
            var OnListDrawItemMethod = ColorUIType.GetMethod("OnListDrawItem",
                BindingFlags.NonPublic | BindingFlags.Instance);
            var OnListKeyDownMethod = ColorUIType.GetMethod("OnListKeyDown",
                BindingFlags.NonPublic | BindingFlags.Instance);
    
            //Color UI Control
            var colorUi = (Control)ColorUiConstructor.Invoke(new[] { this });
            ColorUiField.SetValue(this, colorUi);
    
            //Custom colors ListBox
            var listBox = (ListBox)Activator.CreateInstance(ColorEditorListBoxType);
            //Colors
            listBox.Items.AddRange(new object[] { Color.Red, Color.Green, Color.Blue });
            listBox.DrawMode = DrawMode.OwnerDrawFixed;
            listBox.BorderStyle = BorderStyle.FixedSingle;
            listBox.IntegralHeight = false;
            listBox.Sorted = false;
            listBox.Click += (sender, e) =>
                OnListClickMethod.Invoke(colorUi, new[] { sender, e });
            //Custom paint
            listBox.DrawItem += OnListDrawItem;
            //Original paint
            //listBox.DrawItem +=(sender, e) =>
            //    OnListDrawItemMethod.Invoke(colorUi, new[] { sender, e });
            listBox.DrawItem += OnListDrawItem;
            listBox.KeyDown += (sender, e) =>
                OnListKeyDownMethod.Invoke(colorUi, new[] { sender, e });
            listBox.Dock = DockStyle.Fill;
    
            //Add the custom tab page, including the custome colors
            var tabControl = colorUi.Controls.OfType<TabControl>().First();
            var customTabPage = new TabPage();
            customTabPage.Text = "Theme";
            customTabPage.Controls.Add(listBox);
            tabControl.TabPages.Add(customTabPage);
            return base.EditValue(context, provider, value);
        }
        private void OnListDrawItem(object sender, DrawItemEventArgs e)
        {
            var colorNames = new Dictionary<int, string>
            {
                {Color.Red.ToArgb(), "Blood"},
                {Color.Green.ToArgb(), "Life potion"},
                {Color.Blue.ToArgb(), "Water"},
            };
            ListBox lb = (ListBox)sender;
            object value = lb.Items[e.Index];
            e.DrawBackground();
            this.PaintValue(value, e.Graphics,
                new Rectangle(e.Bounds.X + 2, e.Bounds.Y + 2, 22, e.Bounds.Height - 4));
            e.Graphics.DrawRectangle(SystemPens.WindowText,
                new Rectangle(e.Bounds.X + 2, e.Bounds.Y + 2, 22 - 1, e.Bounds.Height - 4 - 1));
            var color = (Color)value;
            var name = colorNames.ContainsKey(color.ToArgb()) ? colorNames[color.ToArgb()] : color.Name;
            using (var foreBrush = new SolidBrush(e.ForeColor))
                e.Graphics.DrawString(name, lb.Font, foreBrush, e.Bounds.X + 26, e.Bounds.Y);
        }
    }
    public class MyControl : Control
    {
        [Editor(typeof(CustomColorEditor), typeof(UITypeEditor))]
        public Color MyColor { get; set; }
    }
    

    Note: It's relying on implementation details of the color editor, for example, in .NET 5+, the member field name is "_colorUI".