1

I am deep into using the Winforms designer (System.ComponentModel.Design namespace) in my C#/.NET solution so that my users have access to a form designer within my running application. Much of it works well, but I ran into a very specific problem: I encountered a property on a Microsoft control that appears only during design-time--i.e., for the design-time instance of the control. I want to suppress that property so that users cannot modify it when they place an instance of that control on my program's implementation of the Winform design surface.

Details: When a user drag-and-drops a control from the toolbox to the designer surface, I ensure that the newly added designer instance of the control is selected (so that it present resize handles and so the property grid displays that control's design-time properties). I bind the selected objects on the designer surface to the property grid by using the selection service's GetSelectedComponents() method and assigning the property grid's SelectedObjects property to the result.

Many of the controls on my toolbox are .NET controls. One of them is the .NET Winforms TableLayoutPanel control. When you place an instance of that control on a designer surface, you will see a Columns property in the bound PropertyGrid. I would like to suppress that property so that it doesn't appear in the PropertyGrid.

The issue is that this Columns property doesn't appear to exist in the properties list for the TableLayoutPanel type--so using selectedComponents[0].GetType().GetProperties(), for example, doesn't contain a Columns property. Also, I cannot create a new or override for the existing Columns property because it doesn't appear as an exposed property for the TableLayoutPanel control at design time--thus I cannot decorate it with the Browsable(false) attribute.

I can't seem to leverage PreFilterProperties or PostFilterProperties because I can't interact and customize the TableLayoutPanel's designer.

How can I suppress the Columns designer property for the TableLayoutPanel so that it doesn't appear in the PropertyGrid without having to write my own designer?

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Jazimov
  • 12,626
  • 9
  • 52
  • 59
  • 2
    The property name is `ColumnStyles` if it helps. `Columns` is its display name. – Reza Aghaei Oct 07 '19 at 17:58
  • Wow, I didn't think of that and I'm surprised that Microsoft decided to expose ColumnStyles as a property name other than ColumnStyles (I wonder why they did that?) Is the only way I could have discovered this would have been to browse the .NET source and look for the display name attribute for the ColumnStyles property? – Jazimov Oct 08 '19 at 15:01
  • 1
    PropertyGrid always shows the display name which is set by `DisplayName` attribute for a property. It's just for display purpose in PropertyGrid, DataGridView or any other component/framework which uses that attribue. When you press F12 or go to definition of a member, VS shows its attributes as well. – Reza Aghaei Oct 08 '19 at 17:48

1 Answers1

3

If you are trying to avoid writing TableLayoutPanelDesigner yourself, then here is a workaround that I can suggest.

ITypeDescriptorFilterService is the interface which is responsible for calling PreFilterProperties method of the designer. The DesignSurface class has a an instance of an implementation of this interface registered in its ServiceContainer. So you can remove the existing registered instance and register a new instance of your own implementation of ITypeDescriptorFilterService.

In the new implementation, override FilterProperties and do whatever you think is suitable, for example you can check if the type of the component is TableLayoutPanel, then don't call its designer PreFilterProperties.

Then to wrap up the solution, you need to create your own design surface by deriving from DesignSurface class and removing the registered ITypeDescriptorFilterService and registering the desired instance which you created.

Example

Just for your information, the name of the property which you are looking for is ColumnStyles and it has Browsable(false) attribute by default. But the default designer of TableLayoutPanel replaces this property with a browsable version.

What I've done in this example is stopping the designer from making those properties Visible.

First provide a custom implementation of ITypeDescriptorFilterService. The following one is in fact the existing implementation in System.Design assembly which I've changed its FilterProperties method and checked if the component is TableLayoutPanel, I've asked to do nothing.

using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;
public class TypeDescriptorFilterService : ITypeDescriptorFilterService
{
    internal TypeDescriptorFilterService()
    {
    }

    private IDesigner GetDesigner(IComponent component)
    {
        ISite site = component.Site;
        if (site != null)
        {
            IDesignerHost service = site.GetService(typeof(IDesignerHost)) as IDesignerHost;
            if (service != null)
                return service.GetDesigner(component);
        }
        return (IDesigner)null;
    }

    bool ITypeDescriptorFilterService.FilterAttributes(IComponent component, IDictionary attributes)
    {
        if (component == null)
            throw new ArgumentNullException("component");
        if (attributes == null)
            throw new ArgumentNullException("attributes");
        IDesigner designer = this.GetDesigner(component);
        if (designer is IDesignerFilter)
        {
            ((IDesignerFilter)designer).PreFilterAttributes(attributes);
            ((IDesignerFilter)designer).PostFilterAttributes(attributes);
        }
        return designer != null;
    }

    bool ITypeDescriptorFilterService.FilterEvents(IComponent component, IDictionary events)
    {
        if (component == null)
            throw new ArgumentNullException("component");
        if (events == null)
            throw new ArgumentNullException("events");
        IDesigner designer = this.GetDesigner(component);
        if (designer is IDesignerFilter)
        {
            ((IDesignerFilter)designer).PreFilterEvents(events);
            ((IDesignerFilter)designer).PostFilterEvents(events);
        }
        return designer != null;
    }

    bool ITypeDescriptorFilterService.FilterProperties(IComponent component, IDictionary properties)
    {
        if (component == null)
            throw new ArgumentNullException("component");
        if (properties == null)
            throw new ArgumentNullException("properties");
        if (typeof(TableLayoutPanel).IsAssignableFrom(component.GetType()))
            return true;
        IDesigner designer = this.GetDesigner(component);
        if (designer is IDesignerFilter)
        {
            ((IDesignerFilter)designer).PreFilterProperties(properties);
            ((IDesignerFilter)designer).PostFilterProperties(properties);
        }
        return designer != null;
    }
}

Then create a design surface by deriving from DesignSurface:

using System;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;
public class MyDesignSurface : DesignSurface
{
    public MyDesignSurface() : base()
    {
        this.ServiceContainer.RemoveService(typeof(ITypeDescriptorFilterService));
        this.ServiceContainer.AddService(typeof(ITypeDescriptorFilterService), new TypeDescriptorFilterService());
    }
}

Then for example initialize the design surface this way:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        var surface = new MyDesignSurface();
        var host = (IDesignerHost)surface.GetService(typeof(IDesignerHost));
        var selectionService = (ISelectionService)surface.GetService(typeof(ISelectionService));
        surface.BeginLoad(typeof(Form));
        var root = (Form)host.RootComponent;
        var tableLayoutPanel1 = (Control)host.CreateComponent(typeof(TableLayoutPanel), "tableLayoutPanel1");
        root.Controls.Add(tableLayoutPanel1);
        var view = (Control)surface.View;
        view.Dock = DockStyle.Fill;
        this.Controls.Add(view);
        selectionService.SetSelectedComponents(new[] { tableLayoutPanel1 });
        var propertyGrid1 = new PropertyGrid() { Dock = DockStyle.Right, Width = 200 };
        this.Controls.Add(propertyGrid1);
        propertyGrid1.SelectedObjects = selectionService.GetSelectedComponents().Cast<object>().ToArray();
    }
}

Then run your designer application and you will see Columns and Rows properties are hidden as expected.

You need to hide ColumnCount and RowCount properties and also the verbs assigned to editing/adding/removing columns and rows.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • 1
    Reza, awesome answer (as always from you)--one that I'll accept as correct for now while I spend a little bit of time digesting and implementing what you've suggested. – Jazimov Oct 08 '19 at 15:02
  • Reza, I've had time to get to this (apologies for the delay): I have FilterProperties properly hooked, but I don't see in your example above how the TableLayoutPanel Columns and Rows properties are hidden. Where is that done? – Jazimov Nov 13 '19 at 15:57
  • 1
    No problem at all. Look at this piece of code in `FilterProperties`: `if (typeof(TableLayoutPanel).IsAssignableFrom(component.GetType())) return true;` It means do not run `PreFilterProperties` or `PostFilterProperties` of `IDesigner`. But why do we may want to do this? Because the property which you are looking for is `ColumnStyles` and it has `Browsable(false)` attribute by default. But the default designer of `TableLayoutPanel` replaces this property with a browsable version in `PreFilterProperties` method of the `TableLayoutDesigner`. – Reza Aghaei Nov 13 '19 at 16:28
  • I thank you--I am going to post a related follow-up question shortly. – Jazimov Nov 13 '19 at 16:37
  • I see that logic now (wow--that wasn't obvious until you explained further). Thank you again. I have a follow-up question posted now: https://stackoverflow.com/questions/58841974/need-to-hide-a-designer-only-property-from-propertygrid-for-a-net-winforms-cont – Jazimov Nov 13 '19 at 16:57