Share via


Creating a Name property on your component

Say you're creating a simple component with a property called Name....

namespace WindowsApplication428 {
public partial class MyComponent : Component {
public MyComponent() {
InitializeComponent();
}

        public MyComponent(IContainer container) {
container.Add(this);
            InitializeComponent();
}

        private string name;

        public string Name {
get { return name; }
set { name = value; }
}
}
}

If you drop "MyComponent" onto the designer, you will see two properties in the property grid - "(Name)" and "Name". The name property with the parenthesis is auto-populated, your name property is not. When you look at the designer generated code, "Name" is not being written out. The question is - how can we unify these two entries in the property grid?

There are really two methods for this. The first one is a runtime solution, the second is a designtime solution.

Solution 1 - Runtime: When the site is populated, fish the name from the site.  

At designtime, the Site property will be non-null, and the name property will be serialized. Use Browsable(false) on the Name property to hide the "Name" property from the property grid.

         [Browsable(false)]
public string Name {
get {
if (this.Site != null && !String.IsNullOrEmpty(this.Site.Name)) {
name = this.Site.Name;
}

return name;
}
set {
name = value;
}
}

Solution 2 - Designer: Shadow the name property in the designer.

When the component is serialized into it's "InitializeComponent" code, it will go through the property descriptors to see what should be serialized. We can replace the property descriptor for the runtime Name property with one that returns the Name from the Site. This is called "shadowing" a property at design time. 

    class NamedComponentDesigner : ComponentDesigner {

        private string Name {
get {
return Component.Site.Name;
}
set {
// don't do anything here during loading, if a refactor changed it we don't want to do anything
IDesignerHost host = GetService(typeof(IDesignerHost)) as IDesignerHost;
if (host == null || (host != null && !host.Loading)) {
Component.Site.Name = value;
}
}
}
protected override void PreFilterProperties(System.Collections.IDictionary properties) {
base.PreFilterProperties(properties);

            string[] shadowProps = new string[] {
"Name"
};

            Attribute[] empty = new Attribute[0];
PropertyDescriptor prop;
for (int i = 0; i < shadowProps.Length; i++) {
prop = (PropertyDescriptor)properties[shadowProps[i]];
if (prop != null) {
properties[shadowProps[i]] = TypeDescriptor.CreateProperty(typeof(NamedComponentDesigner), prop, empty);
}
}

        }

}

Note here that in our class, we still need to make the "Name" property browsable false. If we did not, we would have "(Name)" and "Name", but they would be in sync because of the designer shadowing.

       [Designer(typeof(NamedComponentDesigner))]
public partial class MyComponent : Component {
public MyComponent() {
InitializeComponent();
}

        public MyComponent(IContainer container) {
container.Add(this);

            InitializeComponent();
}

        private string name;

        [Browsable(false)]
public string Name {
get {
return name;
}
set {
name = value;
}
}
}

Obviously the first solution is easier for this case, but I show the latter, as it is more powerful. It essentially means you can change the behaviors of properties at design time without changing runtime code. Note that once you "shadow" properties, the serialization engine will look for ShouldSerialize* methods on the designer, not the runtime class. More info on serialization here and here.