Search code examples
c#.netwinformscustom-componentcodedom

Custom component works in the Designer, but is invisible in code


I have created a custom Component, derived from BindingSource and it seems to work as expected.
When I drop it on a Form, I can set all properties and other controls see it and can use it as datasource for binding. This all works well.

My problem is, when I want to access this component in code, the code editor keeps telling me there is no such component.
How is that possible ?

It shows in the designer, I can set properties, I can let it interact with other controls in the Designer and at runtime it works perfect.
But the code editor cannot find it, it keeps saying:

The name gttDatasource1" does not exists in the current context

What can cause this? How to fix it?

I tried clean/rebuild
I tried restarting VS
I tried restarting the computer

EDIT
Part of the Designer.cs of the Form I dropped the component on:

namespace Test_app
{
    partial class FormLogSCSSalesInvoiceList
    {
        private System.ComponentModel.IContainer components = null;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormLogSCSSalesInvoiceList));
            gttControls.gttDataSource gttDataSource1 = new gttControls.gttDataSource();

The custom component is in the Designer.cs

Part of the code of the custom component

namespace gttControls
{
    internal class gttDataSourceCodeDomSerializer : CodeDomSerializer
    {
        public override object Deserialize(IDesignerSerializationManager manager, object codeObject)
        {
            CodeDomSerializer baseClassSerializer = (CodeDomSerializer)manager.GetSerializer(typeof(gttDataSource).BaseType, typeof(CodeDomSerializer));
            var Result = baseClassSerializer.Deserialize(manager, codeObject);
            ((gttDataSource)Result).CorrectTableColumns();
            return Result;
        }
    }

    public delegate void OnActiveChangedHandler(object sender, EventArgs e);
    public delegate void OnBeforeUpdateHandler(object sender, EventArgs e);

    [DesignerSerializer(typeof(gttDataSourceCodeDomSerializer), typeof(CodeDomSerializer))]
    public partial class gttDataSource : BindingSource
    {
        private readonly gttDataTable _gttDataTable;
        private GttTableProperties _gttTableProperties;
        private Collection<gttDataTableColumn> _columns = new Collection<gttDataTableColumn>();
        private bool _active = false;
        private readonly bool _refreshSchema = false;

        public event ActiveChangedHandler ActiveChanged;
        public event BeforeUpdateHandler BeforeUpdate;

        public gttDataSource()
        {
            _gttTableProperties = new GttTableProperties(this);
            _gttDataTable = new gttDataTable();
            DataSource = _gttDataTable.Table;
            _gttDataTable.BeforeUpdate += _gttDataTable_BeforeUpdate;
        }
    }
}

EDIT 2
When I try to use this Component in code I get this:

enter image description here

And to proof the Component is actually in the Form's Designer:

enter image description here

EDIT 3
I added a ctor as suggested in the comments:

public gttDataSource(IContainer container) : this() 
{ 
    if (container == null) 
    { 
        throw new ArgumentNullException("container is null"); 
    } 
    container.Add(this);
}

But it does not help. This constructor does not gets called when dropping the component on the form, or at any other time.


Solution

  • Symptoms:
    A Custom Component that implements a Custom CodeDomSerializer, when added to a Form Container, generates an Object that is not accessible from the Form class that contains it and should define and generate the Component's Instance (as a private Field).

    The issue is related to the custom CodeDomSerializer implementation.
    A custom serializer that implements the base CodeDomSerializer must override both the Deserialize() and the Serialize() methods.
    From the Remarks section of the documentation (note must, not should):

    To implement a custom CodeDomSerializer for a type, you must:

    • Define a class that derives from CodeDomSerializer.

    • Implement method overrides for serialization or deserialization methods.

    • Associate your custom CodeDomSerializer implementation with a type of component using a DesignerSerializerAttribute.

    For the default serializer to generate code statements that configure a Component / Control in the standard way, we must call the base serializer for the component.
    Otherwise, the serializer doesn't perform the full serialization and just creates a local object using the Type that it can access.
    Thus, it won't create an associated Field and also the serialization of Property values, assigned in the Designer, won't follow the standard logic (Properties of this object may be found scattered through the Designer.cs file).

    It wasn't clear, in the question, that the code posted here was the complete CodeDomSerializer implementation.
    To work as intended, the custom CodeDomSerializer must include the Serialize() method override and specify what Type(s) should be serialized.
    Calling then the default Serialize() method of the base class, generates standard serialization of the Componenent, which is now assigned to an instance Field and is then accessible in the Container Form class:

    internal class gttDataSourceCodeDomSerializer : CodeDomSerializer
    {
        public override object Deserialize(IDesignerSerializationManager manager, object codeObject)
        {
            CodeDomSerializer baseClassSerializer = (CodeDomSerializer)manager.GetSerializer(typeof(gttDataSource).BaseType, typeof(CodeDomSerializer));
            var Result = baseClassSerializer.Deserialize(manager, codeObject);
            ((gttDataSource)Result).CorrectTableColumns();
            return Result;
        }
    
        public override object Serialize(IDesignerSerializationManager manager, object value)
        {
            var serializer = (CodeDomSerializer)manager.GetSerializer(typeof(gttDataSource).BaseType, typeof(CodeDomSerializer));
            return serializer.Serialize(manager, value);
        }
    }
    

    It's also important that Components provide a Constructor that accepts an IContainer object. This is used to properly dispose of Components, since the default Dispose() method of a Form's base class (Control) only considers child Controls, not Components.
    The CodeDomSerialzier considers this Constructor and adds in Designer.cs:

    this.gttDataSource1 = new gttControls.gttDataSource(this.components);
    // [...]
    private gttControls.gttDataSource gttDataSource1;
    

    it also creates, if no other Component has been added before:

     this.components = new System.ComponentModel.Container();
    

    so the Dispose() override of the Form class takes care of this:

    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }
    

    Simple implementation:

    public partial class gttDataSource : BindingSource
    {
        public gttDataSource() => InitializeComponent();
    
        public gttDataSource(IContainer container) : this()
        {
            if (container is null) { 
                throw new ArgumentNullException("container is null"); 
            }
            container.Add(this);
        }
    }