I'm trying to make a WinForms usercontrol with a Collection<T> as a property (Where T stands for some custom classes). I already read a lot about this topic, however I can't let this work properly at design time (at runtime everything works fine). To be more precise: the collection editor shows fine when I click on the "..." button in the property window and I can add and delete items. But when I click the OK button nothing happens and when I reopen the collection editor, all items are lost. When I take a look at the designer file, I see my property is assigned to null, instead of the composed collection. I'll show you the most important code:
UserControl:
[Browsable(true),
Description("The different steps displayed in the control."),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
Editor(typeof(CustomCollectionEditor), typeof(UITypeEditor))]
public StepCollection Steps
{
get
{
return wizardSteps;
}
set
{
wizardSteps = value;
UpdateView(true);
}
}
StepCollection class:
public class StepCollection : System.Collections.CollectionBase
{
public StepCollection() : base() { }
public void Add(Step item) { List.Add(item); }
public void Remove(int index) { List.RemoveAt(index); }
public Step this[int index]
{
get { return (Step)List[index]; }
}
}
Step class:
[ToolboxItem(false),
DesignTimeVisible(false),
Serializable()]
public class Step : Component
{
public Step(string name) : this(name, null, StepLayout.DEFAULT_LAYOUT){ }
public Step(string name, Collection<Step> subSteps) : this(name, subSteps, StepLayout.DEFAULT_LAYOUT){ }
public Step(string name, Collection<Step> subSteps, StepLayout stepLayout)
{
this.Name = name;
this.SubSteps = subSteps;
this.Layout = stepLayout;
}
// In order to provide design-time support, a default constructor without parameters is required:
public static int NEW_ITEM_ID = 1;
public Step()
: this("Step" + NEW_ITEM_ID, null, StepLayout.DEFAULT_LAYOUT)
{
NEW_ITEM_ID++;
}
// Some more properties
}
CustomCollectionEditor:
class CustomCollectionEditor : CollectionEditor
{
private ITypeDescriptorContext mContext;
public CustomCollectionEditor(Type type) : base(type) { }
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
mContext = context;
return base.EditValue(context, provider, value);
}
protected override object CreateInstance(Type itemType)
{
if (itemType == typeof(Step))
{
Step s = (Step)base.CreateInstance(itemType);
s.parentContext = mContext; // Each step needs a reference to its parentContext at design time
return s;
}
return base.CreateInstance(itemType);
}
}
The things I already tried:
- Making the Step class a component as described here: http://www.codeproject.com/Articles/5372/How-to-Edit-and-Persist-Collections-with-Collectio
- Changing
Collection<Step>to a custom collection classStepCollectioninheriting System.Collections.CollectionBase (also described in the previous code project article) - Setting the DesignerSerializationVisibility to Content as described here: Collection Editor within a User Control at Design Time When it is set to Visible, the designer assigns null to my property ; when it is set to Content, the designer assigns nothing.
- I also found this: How to make a UserControl with a Collection that can be edited at design time? but the CollectionBase class does this for me already.
- Debugging a lot, but since there are no exceptions I really don't know what's going wrong. When I added an event listener to the collectionForm's closing event, I could see the EditValue property (of the collectionForm) was still null even when I added a few steps in the collection editor. But I also don't know why that is...
When finishing this post, I just found this topic: Simplest way to edit a collection in DesignMode? It's exactly the same problem I experience, however I can't use the proposed answer because I don't use a standard collection.
The articles mentioned by Reza Aghaei are really interesting. However I think I'm close at a more simple solution to my problems:
As I already noticed, the EditValue property of the collectionForm stayed null, despite of adding items to the collection. Now, I'm not actually sure what happens inside the EditValue method of the collection editor, but I guess it catches an exception because my initial value of my collection is null (it is not initialized in the constructor) and thus returning null instead of creating a new collection. By making the following changes in my custom collection editor class, I get very promising results:
Notice the second line inside the method, which assigns a new Collection to the initial value. By doing this, my collection is persisted and everything works almost fine.
The only thing I want to fix now is the serialization to the designer file. Currently something like this is produced:
This code will give an exception because wizardStepsControl1.Steps is never initialized to a collection. What i would like to be produced is something like this:
Even better would be that the whole collection is initialized at first and afterwards assigned to my control's Steps property. I'll see what I can do to let it work and post some updates here, maybe it is required for this to implement an InstanceDescriptor or make my custom Collection class inherit from Component (since components are always initialized in the designer files).
I know this is a totally different question than my first one, so maybe I'll start a new one for this. But if someone knows the answer already it would be great to hear it here!
UPDATE: I found a solution to my problem.
Inheriting from Component and from CollectionBase wasn't possible because it is not allowed by C#. Implementing a TypeConverter which converts my custom collection to an InstanceDescriptor didn't work too (I don't know why, I guess it's because a Collection is serialized in a different way than a normal custom class).
But by creating a
CodeDomSerializer, I was able to add code to the produced designer's code. This way I was able to initialize my collection if some items were added to it during design time:By relating this serializer to my custom control with the
DesignerSerializerAttribute, the following code is produced in the designer file:which is exactly what I wanted.
I took most of this code from https://msdn.microsoft.com/en-us/library/system.componentmodel.design.serialization.codedomserializer(v=vs.110).aspx