4

I have two System.ComponentModel classes, ComponentA and ComponentB:

public class ComponentA : Component
{
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public ComponentB ComponentB { get; } = new ComponentB();

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public Timer TimerA { get; } = new Timer();
}

public class ComponentB : Component
{
    //[Browsable(false)]
    public event EventHandler ComponentBEvent;

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public Timer TimerB { get; } = new Timer();
}

Notice how class ComponentA has a property of type ComponentB and that class ComponentB has a property of type Timer (a System.Windows.Forms.Timer). ComponentA also has a property of type Timer although that is not relevant yet. ComponentB also has an event (ComponentBEvent), this fact being key to what I discovered.

As is, those classes will produce what I am looking for, which is the ability to assign event handlers to events in nested components by using the Properties Window. The following screenshot of the Events tab of the Properties window shows how it is possible to assign an event handler to the Tick event of the TimerB property in ComponentB.

It is possible to assign event handlers to nested components by using the Properties Window

The source code for ComponentB above has the Browsable attribute for the event ComponentBEvent commented out. Uncommenting the attribute as in:

public class ComponentB : Component
{
    [Browsable(false)]
    public event EventHandler ComponentBEvent;

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public Timer TimerB { get; } = new Timer();
}

has the unfortunate effect that ComponentB completely disappears from the Events tab of the Properties window making it impossible to assign an event handler to the TimerB property of ComponentB.

When the containing component does not include an event then it disappears from the Events tab in the Properties Window

So to summarize, what I found is that to be able to use the Properties tab in the Properties Window to assign an event handler to a component that has a 3rd level of nesting (TimerB) it is required for the containing component (ComponentB) to have an event (ComponentBEvent in the example). This is only a problem for components with a 3rd level of nesting (or greater I presume). The class ComponentA does not have any events but this does not preclude one from assigning event handlers to its TimerA property as the first screenshot shows.

The issue I encountered only affects the Events tab of the Properties Window. The Properties tab displays properties for all components as expected.

The Properties tab of the Properties Window is unaffected

My question is, how can I use the Properties tab of the Properties Window to assign event handlers to a component with a 3rd level of nesting and not be forced to having an event in its containing component?

My tests were done with a Windows Forms application in a .NET Framework 4.7.2 project.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
akalnay
  • 61
  • 2
  • There should be a way, but I think it will not be an easy way. In addition to the workaround suggested in the below answer, I can suggest two other workaround: **1)** Every component has a `Disposed` event which is a valid event but it's not browseable, you can make it browsable (by shadowing the event). **2)** Add a dummy browsable but readonly event. It will be shown in the property grid but you cannot assign any event handler to it. – Reza Aghaei Jan 21 '20 at 15:01
  • Following your suggestion, I ended up creating an abstract class that inherits from Component and that contains a browsable shadowed Disposed event. I had the inner classes (ComponentB, etc.) inherit from the abstract class and that worked. I'd still like to find a way that doesn't require adding an event that shows in the Properties Window. Thanks. – akalnay Jan 22 '20 at 12:37

2 Answers2

0

If you would be setting PropertyGrid.SelectedObject yourself, then I'd suggest you to implement CustomTypeDescription anyway (example) to earlier setup an ability to extend it with own attributes (it's very useful).

But since it's a property window of winforms designer, I don't know how to deal with it.

Though you could use a workaround by exposing missing event on an upper level:

public class ComponentA : Component
{
    public event EventHandler ComponentBTimerBTick
    {
        add => ComponentB.TimerB.Tick += value;
        remove => ComponentB.TimerB.Tick -= value;
    }

    ...
}
Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • I extended the example further as per your response but I couldn't replicate what you mention regarding the additional event not helping anymore. (By having the additional event I was able to show Tick events for TimerA, TimerB, and TimerC). – akalnay Jan 22 '20 at 12:28
  • Screw my screenshot, I was playing with `DesignerSerializationVisibility` and accidentaly left it with wrong value for `TimerB`.. this is why I don't see it. I'll edit this confusing observation out, thanks for noticing. – Sinatr Jan 22 '20 at 12:50
  • Thanks for clearing up the confusion. Regarding the CustomTypeDescriptor, if I am following you correctly then I think that would end up showing an additional event in the PropertyGrid / Properties Window. I am trying to avoid having to show extraneous events. – akalnay Jan 22 '20 at 12:58
0

There should be a solution to solve the problem, but I think it will not be an easy one. This post is suggesting a workaround, not a solution.

In addition to the workaround suggested in the other answer, I can suggest two other workarounds:

  1. Every component has a Disposed event which is a valid event but it's not browsable, you can make it browsable (by shadowing the event).
  2. You can add a dummy browsable but readonly event. It will be shown in the property grid but you cannot assign any event handler to it.

Workaround 1 - Making Disposed event of Component browsable

public class ComponentB : Component
{
    [Browsable(true)]
    public new event EventHandler Disposed
    {
        add { base.Disposed += value; }
        remove { base.Disposed -= value; }
    }

    // ... rest of properties ...
 }

Then it will be shown like this:

enter image description here


Workaround 2 - Create a dummy readonly browsable event with a custom display name

public class ComponentB : Component
{
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [ReadOnly(true)]
    [EditorBrowsable(EditorBrowsableState.Never)]
    [DisplayName("Events")]
    [ParenthesizePropertyName(true)]
    public event EventHandler Dummy { add { } remove { } }

    // ... rest of properties ...
 }

Then it will be shown like this:

enter image description here

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Yes, your first workaround is what I referred to in my comment reply to you above except that I used an abstract class and made the inner classes inherit from the abstract class. Separation of concerns and all that :). I'd still like to find a way that doesn't require adding an event that shows in the Properties Window. Thanks. – akalnay Jan 22 '20 at 13:06
  • No problem :) I posted the screenshots as well. – Reza Aghaei Jan 22 '20 at 13:14