Search code examples
c#comboboxoverridingtostring

Why overriden ToString() do not return what I want when item added to ComboBox?


public partial class TestConrol : UserControl
{
    public TestConrol()
    {
        InitializeComponent();
    }

    public override string ToString()
    {
        return "asd";
    }
}

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        TestConrol tc1 = new TestConrol();
        comboBox1.Items.Add(tc1);

        TestConrol tc2 = new TestConrol();
        comboBox1.Items.Add(tc2);
    }
}

When form loaded, I see combobox has two items with empty names, instead of "asd" :/
But this work if I override ToString() in common class, not derived from anything:

public class TestClass
{
    public override string ToString()
    {
        return "bla bla bla";
    }
}

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        TestClass tcl = new TestClass();
        comboBox1.Items.Add(tcl);
    }
}

After that I see in combobox "bla bla bla"


Solution

  • I tried to understand the source code(!). This is not a simple call to ToString().

    There's an internal class System.Windows.Forms.Formatter doing some stuff. It eventually creates a converter. This is roughly equivalent to saying:

    var conv = System.ComponentModel.TypeDescriptor.GetConverter(tc1.GetType());
    

    where tc1 is the TestContol from your question. Now, had we used the TestClass tcl which doesn't implement any interfaces, this would have given us a converter which would eventually call ToString().

    But in this example we use tc1, and it is a System.ComponentModel.IComponent. Our conv therefore becomes a System.ComponentModel.ComponentConverter. It uses the Site of the IComponent. When we say:

    string result = conv.ConvertTo(tc1, typeof(string));
    

    and the Site is null, we get the empty string "" you saw in your combo box. Had there been a Site it would have used its Name instead.

    To demonstrate that, put the following into your TestControl instance constructor:

    public TestConrol()
    {
        InitializeComponent();
        Site = new DummySite(); // note: Site is public, so you can also
                                // write to it from outside the class.
                                // It is also virtual, so you can override
                                // its getter and setter.
    }
    

    where DummySite is something like:

    class DummySite : ISite
    {
        public IComponent Component
        {
            get { throw new NotImplementedException(); }
        }
    
        public IContainer Container
        {
            get { throw new NotImplementedException(); }
        }
    
        public bool DesignMode
        {
            get { throw new NotImplementedException(); }
        }
    
        public string Name
        {
            get
            {
                return "asd";  // HERE'S YOUR TEXT
            }
            set
            {
                throw new NotImplementedException();
            }
        }
    
        public object GetService(Type serviceType)
        {
            return null;
        }
    }