1
votes

I have an Interface with a bunch of properties, some of which are defined as readonly, eg:

public interface IActivity {
  string Id { get; }
  bool IsEnabled { get; }
}

The class that implements this interface has public set methods, for use elsewhere, but the consumer of this interface should not be able to set these properties. I'm now building a test consumer, and exposing the object through a PropertyGrid control. Because it's binding to the class with the public set methods, the properties which are readonly as far as the consumer should be concerned are editable within the grid.

I can fix this problem in two ways, firstly by making the setters internal, or by marking the properties with the [ReadOnly] attribute, however this doesn't seem "right", since in theory something else could implement this interface out of my control, with those supposedly readonly properties then changeable via the property grid.

I tried explicitly casting to the interface when assigning the object to the property grid, but that didn't help either:

propGrid.SelectedObject = (IActivity)obj;

Is there a way to force a PropertyGrid control to respect the contract of the Interface, rather than having to change the concrete classes?

2
The grid probably uses reflection to determine which columns it should display and whether those columns are editable or not. Since the object you are passing in is of a type that allows editing the properties, there isn’t really much you can do about it. And casting it to some more restricted type won’t help because SelectedObject expects an object anyway. - poke
Yeah that's what I figured, it must be reflected given the other attributes you can apply to change the behaviour in the grid. Was just hoping there may be a way to force it into following the interface - James Thorpe

2 Answers

1
votes

I've worked around this by creating a wrapper class that matches the interface specification exactly:

class ActivityWrapper : IActivity {
    private readonly IActivity _activity;
    public ActivityWrapper(IActivity activity) {
        _activity = activity;
    }

    public string Id
    {
        get { return _activity.Id; }
    }
    public bool IsEnabled
    {
        get { return _activity.IsEnabled; }
    }
}

And using that when exposing an object to the property grid:

propGrid.SelectedObject = new ActivityWrapper((IActivity)obj);

I'd still be interested if there is a different, more automatic way of achieving this, rather than maintaining another class.

1
votes

James' answer is correct. I will just add that if your IActivity implements INotifyPropertyChanged you must route the event in such a way:

class ActivityWrapper : IActivity
{
    private readonly IActivity _activity;
    public ActivityWrapper(IActivity activity)
    {
        _activity = activity;
        _activity.PropertyChanged += _activity_PropertyChanged;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    void _activity_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, e);
    }

    public string Id
    {
        get { return _activity.Id; }
    }
    public bool IsEnabled
    {
        get { return _activity.IsEnabled; }
    }
}

And the only way that I see to automate this would be to generate this wrapper using Reflection.Emit.