简体   繁体   中英

C# NestedClass Expanded Property to Refresh/Invalidate Control in Design Mode

I'm trying to reproduce how Font class Nested Property is able to Invalidate() it's containing Control when the values are changed from the Properties window, without a need to lost focus on the form window. Like if you change the Size property, the Form window will be automatically redrawn with a matching Font Size.

The complete code is at the end of this post

  1. The test NestedClass is very simple which has two Properties and uses a custom ExpandableObjectConverter .

  2. The ContainerClass extends Control class. It invokes Control.Invalidate() method when the NestedClass value is changed. Properties of the NestedClass are drawn by overriding Control.OnPaint() method.

Resulting Control when loaded to the Designer : Initial Draw

Changing the values directly from the NestedClass parent property triggers the redraw : Updating from the parent property works

But changing the values from the nested property, does not redraw : Updating from the nested properties doesn't work

If later the Form window is clicked, Invalidate() is triggered : After Form window clicked

Font class does not require extra click in the design window in order to redraw the control. Can we achieve this same behavior with a custom class like this?

For reference I've already looked into :

  1. INotifyPropertyChanged ==> Problem here is that the PropertyChanged event is only subscribed by the container Control when the item is first added. If I close the Form[Design] window and reopens it, the event will not be subscribed again.
  2. RefreshProperties.All or RefreshProperties.Repaint does not seems to do anything.

If all else fails, obviously clicking on the form designer window will solve the problem, but it bugs me that a built-in .NET class can do this, but a custom class cannot. Any suggestions are greatly appreciated.

The Code :

//The Nested Class
[TypeConverter(typeof(NestedClassTypeConverter))]
public class NestedClass
{
    [NotifyParentProperty(true)]
    public string CountryName { get; set; } = "Japan";

    [NotifyParentProperty(true)]
    public string Capital { get; set; } = "Tokyo";

}

//The Container Class
public class ContainerClass : Control
{
    private NestedClass _country = new NestedClass();
    [Category("_Data")]
    public NestedClass Country
    {
        get => _country;
        set
        {
            _country = value;
            this.Invalidate();
        }
    }

    protected override Size DefaultSize => new Size(100, 100);
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        using (Brush b = new SolidBrush(Color.Black))
        {
            e.Graphics.DrawString(Country.CountryName, this.Font, b, new PointF(10, 10));
            e.Graphics.DrawString(Country.Capital, this.Font, b, new PointF(10, 50));
        }
    }

}

//TypeConverter for that fancy Expandable properties
public class NestedClassTypeConverter : ExpandableObjectConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string)
        {
            string[] v = ((string)value).Split(',');
            return new NestedClass
            {
                CountryName = v[0],
                Capital = v[1]
            };
        }
        return base.ConvertFrom(context, culture, value);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        if (destinationType == typeof(NestedClass))
        {
            return true;
        }
        return base.CanConvertTo(context, destinationType);
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            return ((NestedClass)value).CountryName + "," + ((NestedClass)value).Capital;
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }

}

It's funny that after a day of searching without any luck, and finally posted my question, suddenly I found another thread which speaks the very same issue with answer --> Thread with the answer

Just to reiterate, apparently INotifyPropertyChanged is still the answer to my problem. The difference is that I was subscribing to the event on the ContainerClass constructor before. Something like :

public ContainerClass()
{
    NestedClassInstance.PropertyChanged += delegate { this.Invalidate(); };
}

While apparently what needs to done is to subscribe or resubscribe to that event on the NestedClass setter body. Something like :

public class ContainerClass : Control
{
    private NestedClass _nestedClassInstance = new NestedClassInstance();
    public NestedClass NestedClassInstance
    {
        get => _nestedClassInstance;
        set
        {
            if (_nestedClassInstance != null)
                _nestedClassInstance.PropertyChanged -= delegate { this.Invalidate(); };
            _nestedClassInstance = value;
            _nestedClassINstance.PropertyChanged += delegate { this.Invalidate(); };
        }
    }
}

There you have it. End of another coding journey.

EDIT : Actually I'm still thinking whether this is the actual solution or not, since if we're referring to Font class metadata, it does not necessarily uses INotifyProperty or any other sort of EventHandler for a property changed event. But anyway, this does the trick.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM