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
The test NestedClass
is very simple which has two Properties and uses a custom ExpandableObjectConverter
.
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 :
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.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.