Search code examples
c#.netwinformsvisual-studiocollections

How can I insert the EventsTab in the PropertyGrid?


Here is the deal, I have some collections to insert in a validation class and I'm trying to make it able to insert into the Visual Studio's Editor's IDE with all the context explained (if not enough just ask me) I want to the PropertyGrid to show its EventsTab like this in Visual Studio's IDE

EventsTab in PropertyGrid VS Editor

But the problem is I have checked the Winforms source code and for what I could understand I have found that in order to insert the Button for EventsTab we have to call ShowEventsButton(true) a private method of the PropertyGrid but that won't work (called it with reflection).

So what I have tried so far:

  • Calling AddRefTab passing EventsTab with parameters (private method)
  • Calling SetupToolbar with the true parameter (private method)
  • Calling RefreshTabs
  • Setting all TooltipItems to Visible
  • Calling ShowEventsButton(true)

Edit

I have two classes in this schema, one of them is the Validation class which implements the IValidation interface and it has the the events which I want to show in the property grid.

The other class is a ControlValidator which derives from a component and has a list of Validation

Edit 2:

To be more specific what I'm trying to accomplish is to make use of the CollectionEditor (same window that is used to edit the columns of a DataGridView) in this CollectionEditor I want to make it show the EventsTab in its PropertyGrid (actually I want to make it be exactly like the PropertyTab of the Visual Studio IDE but I'm not in that part yet) so the events and property that I want to show is of a list/collection of the ControlValidator (component)

Let me try to make it clearer:

Class Diagram

As you can see the ControlValidator has a collection of the IValidation interface and it is the component so it should and is working like this:

ControlValidator working as it should be

As you can see it does not show the events tab in the IDE because it does not have any events but if you double click the validations property of the controlValidator it opens the CollectionEditor which have a PropertyGrid in there

CollectionEditor

And as a PropertyGrid I'm trying to make it show the eventsTab (which I'm not able to make it show in any of the ways I listed up there)


Solution

  • If I understand correctly, you want to provide a way for the events for a Custom class to show in the VS IDE Property Editor.

    This can be done fairly simply with only a few changes to your class. First, you need to understand that VS actually compiles your forms for use in the IDE designer. Specifically, all that designer code in InitializeComponent needs to be compiled in order to create/draw your form.

    This means in order to for your Class events (and props) to show in the IDE, your class needs to be a Component (or Control) so it can be compiled like any other for the IDE property editor. It cant be a class you instance at runtime because that code is not examined for the form editor.

    I've done this several times with classes that otherwise act as normal classes at runtime but with the added ability to set properties and so forth in the IDE via the from designer (a class with ExtenderProvider abilities, for example) . The changes to your class start with something like this:

    class Validator:Component, ISupportInitialize
    {
        // fake props
        public string Foo { get; set; }
        public int Bar  { get; set; }
    
        public event ValidatingEventHandler Validating;
        public delegate void ValidatingEventHandler(object sender, EventArgs e);
    
        public event ValidatedEventHandler Validated;
        public delegate void ValidatedEventHandler(object sender, EventArgs e);
    
        public Validator(string foo, int bar) 
        { 
        }
        public Validator()
        {
        }
    
        // ISupportInitialize methods
        public void BeginInit()
        { 
        }
        public void EndInit()
        {
        }
    }
    

    Compile and the Validator will be available in the toolbox. Drag one to the form/Component tray. Select it, open the Properties Editor et voila the properties and events show in the IDE:

    enter image description here

    Validator Component Properties (click for larger image). The Events View:

    enter image description here

    Double click the desired event and VS will add the skeleton event handler for you to edit.

    Care and Feeding of Components

    Simply having your class inherit from Component does most of the work. There are a few things related to Components which are required or may come up. Most of these are simple issues and fixes.

    Simple Ctor

    As a Component, your Validator class will have to have a simple (parameterless) ctor. VS has no idea how to instance it using the (string foo, int bar) version above.

    This can cause problems if you have code which relies on those params to initialize other things in the constructor. The answer for that is ISupportIntialize.

    ISupportInitialize

    This interface provides support for a BeginInit and EndInit method which VS will invoke from the designer code since it now takes care of setting your Component's properties:

    // 
    // validator1
    // 
    this.validator1.Bar = 0;
    this.validator1.Foo = null;
    this.validator1.Validating += new Validator.ValidatingEventHandler(this.validator1_Validating);
    ...
    this.Text = "Form1";
    this.Load += new System.EventHandler(this.Form1_Load);
    this.tableLayoutPanel1.ResumeLayout(false);
    ((System.ComponentModel.ISupportInitialize)(this.validator1)).EndInit();
    this.ResumeLayout(false);
    
    ...
    private Validator validator1;          // note: no params
    

    As you can see, one of the last things the designer code does, after all the properties have been set, is invoke EndInit on your class/Component. Any initialization ctor code using foo or bar can be simply be moved to there.

    Properties

    The Properties as well as events for your class will now show in the IDE. If you wish to hide any of these (perhaps it only has meaning when there are actual items to be validated), you can keep them from showing in the Property Pane using the Browsable attribute:

    [Browsable(false)]
    public string Foo { get; set; }
    

    In order to be certain that VS does serialize those you wish to be managed via the IDE, use the DesignerSerialization attribute:

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public int Bar  { get; set; }
    

    Use DesignerSerializationVisibility.Hidden for any property you do not wish VS to serialize.

    Finally, the OP mentions a "collection of items" which the Validator works on. If there is a collection class property on Validator (such as Items), the default collection editor will display for that property. This may or may not be desirable depending on what the items are. You can [Browsable(false)] on this to hide it.

    If you do wish to add/edit items via the IDE, there is more work to do, but that is another kettle of fish.


    Addendum

    This is in regards to the new, emerging desire to access Item Events in the CollectionEditor and (apparently) have code created for them when clicked (created where, I am not sure). There are a number of problems with this both as a design and in practice. I am assuming a structure like this:

    Form.Validator.Items<ValidationItems>
    

    First, the property grid used in the CollectionEditor is an internal one. It is not hard to find on the EditorForm, but simply enabling the event view would not magically give it the power to know what to do when you click an event. Since it is working with the collection, it knows little about the Type owning the collection, and less about the Form hosting the Type.

    The events view is off in the CollectionEditor because it cant add event code to the form (the great-grandparent object of the Items!), because there is no form level object to reference. It cant/wont add events to your collection class code because your code doesnt not have designer files. Finally, the main requirement for a collection property is that the collection implement IList. If your collection property is a plain List or Collection variable, there is no code file for it to modify.

    To enable form events for the items it needs to be reconfigured. As an Item Class, they 'belong' to something else - the Validator class/component. If the Items are also reworked into a Component, form level events become possible:

    [Serializable]
    public class ValidationItem: Component
    {
        [DefaultValue("")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public string DisplayName { get; set; }
    
        [DefaultValue(-1)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public int Value { get; set; }
    
        public event ValidatingEventHandler Validating;
        public delegate void ValidatingEventHandler(object sender, EventArgs e);
    
        public ValidationItem() 
        {
            Name = "New Item";
            Value = 0;
        }
    }   
    

    Now, the Items will exist as a form component and be stored in the Validator's Items collection - similar to a DataGridView's columns. To edit/add events, you still cannot do so from the CollectionEditor for the same reasons as before, but they are available from the IDE Properties Window.

    Select the Item Component and use the Property Window as normal to add the event code to the designer and to the form (very much as with the Validator component in the original) :

    //  (Designer)
    // validationItem1
    // 
    this.validationItem1.DisplayName = "New Item";
    this.validationItem1.Value = 0;
    this.validationItem1.Validating += new ValidationItem.ValidatingEventHandler(this.validationItem1_Validating);
    
    ...
    private ValidationItem validationItem1;     // persistent form var
    

    in the form:

    private void validationItem1_Validating(object sender, EventArgs e)
    {
    
    }
    

    As a component, all the earlier points about them now applies to the items as well.

    The Drawbacks

    a) If there are a lot of Items, there will be a lot of ValidationItems in the Component Tray. The first thing I would ask myself is whether I need the Validator Class and all these items as individual form level objects.

    b) As with controls, if you remove one, it will remove code from the designer, but not the form. Duplicate code can result as you change/update your items (collection contents tend to be more volatile):

    private void validationItem1_Validating(object sender, EventArgs e)
    ...
    private void validationItem1_Validating_1(object sender, EventArgs e)
    ...
    private void validationItem1_Validating_2(object sender, EventArgs e)
    

    This approach seems "noisy" to me. The CollectionClass is perfectly capable of hooking up to the Items' events which are then bubbled up to the form in the form of one single Validator event. Why have 15 or even 5 events to maintain when one may work? Shouldn't consolidating all that be part of the job of the Validator class? Ex:

    private void validator1_ValidationComplete(object sender, 
                         ValidationCompleteEventArgs e) 
    {
    }
    

    ValidationCompleteEventArgs could report which item did or did not validate, when, why, provide a CancelRemaining capability or whatever the form needs to know. This of is not unlike a ListBox or DataGridView which reports events on behalf of the stuff it contains.