So basically I have a custom UserControl containing a private array of Label objects and I want to be able to access exclusively their Text properties from the outside.
I therefore added a property which type LabelTextCollection is an implementation of IEnumerable and has my Label array as its inner list. Furthermore, I added an implementation of UITypeEditor to allow editing from the windows forms designer.
To try it out, I added my control in a form and edited the property's value. All of that works fine until I close and reopen the designer and the labels take back their default values.
After looking around it seems I have to add an implementation of CodeDomSerializer to allow my type to succesfully serialize into the {Form}.Designer.cs file at design time. I tried serializing a comment line first to test it out but no code is generated.
My final goal would be to have a line like
this.{controlName}.Titles.FromArray(new string[] { "Whatever" } )
added at design time after the property was modified using my editor. What am I misunderstanding and/or doing wrong ?
Custom Type
[DesignerSerializer(typeof(LabelTextCollectionSerializer), typeof(CodeDomSerializer))]
public class LabelTextCollection : IEnumerable<string>, IEnumerable
{
private Label[] labels;
public LabelTextCollection(Label[] labels)
{
this.labels = labels;
}
public void SetLabels(Label[] labels)
{
this.labels = labels;
}
public IEnumerator<string> GetEnumerator()
{
return new LabelTextEnum(labels);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new LabelTextEnum(labels);
}
public string this[int index]
{
get { return labels[index].Text; }
set { labels[index].Text = value; }
}
public override string ToString()
{
if (labels.Length == 0) return string.Empty;
else
{
StringBuilder sb = new StringBuilder("{ ");
foreach (string label in this)
{
sb.Append(label);
if (label == this.Last()) sb.Append(" }");
else sb.Append(", ");
}
return sb.ToString();
}
}
public string[] ToArray()
{
string[] arr = new string[labels.Length];
for (int i = 0; i < labels.Length; i++) arr[i] = labels[i].Text;
return arr;
}
public void FromArray(string[] arr)
{
for(int i = 0; i < arr.Length; i++)
{
if (i >= labels.Length) break;
else labels[i].Text = arr[i];
}
}
public class LabelTextEnum : IEnumerator<string>, IEnumerator
{
private readonly Label[] labels;
private int position = -1;
public LabelTextEnum(Label[] labels)
{
this.labels = labels;
}
public object Current
{
get
{
try
{
return labels[position].Text;
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
string IEnumerator<string>.Current { get { return (string)Current; } }
public void Dispose()
{
return;
}
public bool MoveNext()
{
return ++position < labels.Length;
}
public void Reset()
{
position = -1;
}
}
}
Type Editor
public class LabelTextCollectionEditor : UITypeEditor
{
IWindowsFormsEditorService _service;
IComponentChangeService _changeService;
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (provider != null)
{
_service = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
_changeService = (IComponentChangeService)provider.GetService(typeof(IComponentChangeService));
if (_service != null && _changeService != null && value is LabelTextCollection)
{
LabelTextCollection property = (LabelTextCollection)value;
LabelTextCollectionForm form = new LabelTextCollectionForm() { Items = property.ToArray() };
if (_service.ShowDialog(form) == DialogResult.OK)
{
property.FromArray(form.Items);
value = property;
_changeService.OnComponentChanged(value, null, null, null);
}
}
}
return value;
}
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
}
Serializer
public class LabelTextCollectionSerializer : CodeDomSerializer
{
public override object Serialize(IDesignerSerializationManager manager, object value)
{
var baseSerializer = (CodeDomSerializer)manager.GetSerializer( typeof(LabelTextCollection).BaseType, typeof(CodeDomSerializer));
object codeObject = baseSerializer.Serialize(manager, value);
if (codeObject is CodeStatementCollection && value is LabelTextCollection)
{
var col = value as LabelTextCollection;
var statements = (CodeStatementCollection)codeObject;
statements.Add(new CodeCommentStatement("LabelTextCollection : " + col.ToString()));
}
return codeObject;
}
}
Property of custom Type
[Category("Appearance")]
[Editor(typeof(LabelTextCollectionEditor), typeof(UITypeEditor))]
public LabelTextCollection Titles { get; }
EDIT :
I added a set to my Titles property and set up my project for design-time debugging, I then realized that an exception was thrown on the line
object codeObject = baseSerializer.Serialize(manager, value);
stating that the Label type isn't marked as [Serializable].
I'm assuming that the base serializer is trying to write a call to my LabelTextCollection constructor and to serialize the labels field as a parameter of it.
I tried replacing the line with
object codeObject = new CodeObject();
which got rid of the exception but didn't write anything in the designer.cs file.
I'm (once again) assuming that nothing is happening because there is no relation between the CodeObject I just created and the file (unless that relation is established after it's returned by the Serialize method ?).
As you can probably tell, I'm pretty new regarding the CodeDom stuff so how should I create this object properly ?
EDIT 2 :
I'm so dumb... I forgot the codeObject is CodeStatementCollection test...
So the comment line is writing fine, now all I need to do is to write the correct line with CodeDom and it should work fine.
If someone wants to help, I currently have added to the designer.cs file :
this.FromArray( new string[] { "TEST" } );
So I'm missing the control's and the property's names to get to my final goal.
I'll answer my own post to recapitulate what I did to fix it when that's done.