1

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);
    }

}
4

1 回答 1

0

有趣的是,经过一天没有任何运气的搜索,最后发布了我的问题,突然我发现另一个线程与答案说出了相同的问题--> 带有答案的线程

重申一下,显然INotifyPropertyChanged仍然是我问题的答案。不同之处在于我之前订阅了ContainerClass构造函数上的事件。就像是 :

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

虽然显然需要做的是在NestedClasssetter 主体上订阅或重新订阅该事件。就像是 :

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(); };
        }
    }
}

你有它。结束另一个编码之旅。

编辑: 实际上我仍在考虑这是否是实际的解决方案,因为如果我们指的是 Font 类元数据,它不一定使用INotifyProperty或任何其他类型的 EventHandler 来处理属性更改事件。但无论如何,这可以解决问题。

于 2018-07-13T17:04:14.537 回答