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

1

4

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?

Sansculotte answered 10/12, 2022 at 15:33 Comment(4)
There's nothing to override. ColorUI is a Control Type, it builds a TabControl, adds TabPages and also creates a ColorDialog (which, believe it or not, comes from the ancient VB6 ColorDialog , dlg-4300.dlg, retrieved from resources with Assembly.GetManifestResourceStream() ) -- You'd need to recreate the entire super-class and build your own ColorUI, there's no Service you can call for this. Probably better generate your own custom editorStallings
@Stallings do you mean something like this https://mcmap.net/q/1177533/-custom-color-palette-in-visual-studio-color-property-editor ?Sansculotte
Worse than that. There Reza is replacing the customColors Field with a custom one and injecting in the Constructor of the ColorUI class a custom ColorEditor (let's say, the super-class, or the handler). Don't mind the clever approach to add an Editor Type to VS using a Package, you don't need that, because what you want to do is to parent a TabPage, to add it to the TabControl of ColorUI, then handle this added interface. Not saying that it's impossible (what is?), but the amount of debugging (don't forget the COM dialog and the Hooking thing) is most probably not worth the effortStallings
As mentioned, it's probably simpler to re-create the ColorEditor UITypeEditor entirely. Then, since you have your own ColorUI Control to manage, you can add to it whatever you want. After that, register your UITypeEditor as usual. If you don't have the ColorEditor class (since it appears it's not been published in the .NET source code directly), take it from this PasteBin (if you're interested, let me know. Note that I'll delete this comment in a while)Stallings
C
6

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".

Conover answered 13/12, 2022 at 13:55 Comment(8)
Thank you so much Reza!!! :) Regarding to your suggestion, I have already create a "ThemeManager" component. I just need this extra Tab so to list all my theme colors there, in case where user wants to use them anywhere else. Only one question. I tried to add one of my theme colors listBox.Items.AddRange(new object[] { CmosUI.Theme.Custom.BackColor.Value });. At the ListBox, the title of color looks like this fff0f0f0 instead of 240; 240; 240. Any idea how can I change this? Or how can I give it a name? Let me know If I should make a separate question for this.Sansculotte
Here is what I mean... drive.google.com/file/d/18xn-euYI3yKlYvLBYf-mXwIMlGwADICH/…Sansculotte
As I mentioned in the post 'if you need to used custom names for the colors, then you need more customization in DrawItem'. You can copy the piece of code from source code of ColorEditor and customize it.Conover
You can also find the source code of the ColorEditor in the link which is shared in comments (.NET 4.X) or in WinForms repository (.NET 7, but I guess you can use in .NET 4.X with small changes).Conover
Sorry!!! In the excitement and my haste to check and use your code, I "lost" the part where you were writing about "custom names". :)) Thank you very much for the links!!! Should I customize private void OnListDrawItem inside public override object EditValue? I mean, where should I place it?Sansculotte
Assign the proper event handler in this line of code: listBox.DrawItem += (sender, e) => OnListDrawItemMethod.Invoke(colorUi, new[] { sender, e });. In above example, what I've done is just calling the OnListClick method of the ColorUI class. What you can do is assigning it to a new event handler, doing the custom paint. The custom event handler should belong to your CustomColorEditor, and the body of the method should be something like the body of OnListDrawItem which I linked in my first comment.Conover
See the updated code + screenshot.Conover
A "Thank you" isn't enough dear Reza!!! :)Sansculotte

© 2022 - 2024 — McMap. All rights reserved.