views:

70

answers:

4

(I've answered the question below with a hack. I'm fairly confident in it unless MS change the way that codedom serializers the designer code.)

ETA2:

I've worked out what is going on. I wondered why sometimes it would work and not others. It boils down to the name that I give to the internal property and the collection.

If I rename the property 'Annoyance' to 'WTF', it will serialize correctly because 'WTF' is, alphabetically, after the name of the collection - 'InternalAnger'.

It looks like the serializer is creating instances of objects alphabetically and needs my internal property to be created by the time it comes to create the collection.

I can fix this with a rename, but that's a hack and I fear that writing a custom serializer is a big job - which I've never done before.

Any ideas?


ETA: Jesus, I'm sick of this. This problem was specifically about persisting an interface collection but now on further testing it doesn't work for a normal collection. Here's some even simpler code:

Public Class Anger
End Class

Public Class MyButton
    Inherits Button

Private _Annoyance As List(Of Anger)
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
Public ReadOnly Property Annoyance() As List(Of Anger)
    Get
        Return _Annoyance
    End Get
End Property

Private _InternalAnger As Anger
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
Public ReadOnly Property InternalAnger() As Anger
    Get
        Return Me._InternalAnger
    End Get
End Property

Public Sub New()
    Me._Annoyance = New List(Of Anger)
    Me._InternalAnger = New Anger
    Me._Annoyance.Add(Me._InternalAnger)
End Sub

End Class

The designer screws up the persistence code in the same way as the original problem.

---- Original Problem

The easiest way to explain this problem is to show you some code:

Public Interface IAmAnnoyed
End Interface

Public Class IAmAnnoyedCollection
    Inherits ObjectModel.Collection(Of IAmAnnoyed)
End Class

Public Class Anger
    Implements IAmAnnoyed
End Class

Public Class MyButton
    Inherits Button

Private _Annoyance As IAmAnnoyedCollection
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
Public ReadOnly Property Annoyance() As IAmAnnoyedCollection
    Get
        Return _Annoyance
    End Get
End Property

Private _InternalAnger As Anger
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
Public ReadOnly Property InternalAnger() As Anger
    Get
        Return Me._InternalAnger
    End Get
End Property

Public Sub New()
    Me._Annoyance = New IAmAnnoyedCollection
    Me._InternalAnger = New Anger
    Me._Annoyance.Add(Me._InternalAnger)
End Sub

End Class

And this is the code that the designer generates:

Private Sub InitializeComponent()
    Dim Anger1 As Anger = New Anger
    Me.MyButton1 = New MyButton
    '
    'MyButton1
    '
    Me.MyButton1.Annoyance.Add(Anger1)
    // Should be: Me.MyButton1.Annoyance.Add(Me.MyButton1.InternalAnger)
    '
    'Form1
    '
    Me.Controls.Add(Me.MyButton1)

End Sub

I've added a comment to the above to show how the code should have been generated.

Now, if I dispense with the interface and just have a collection of Anger, then it persists correctly.

Any ideas?

A: 

I'm a bit out of my element here, but shouldn't you have a private setter on that readonly property? I mean, it can't serialize what it can't set, correct?

JerKimball
The readonly property is initialized in the constructor. It doesn't need a setter, elements are simply added to and removed from it using the usual Add and Remove methods.
Jules
A: 

Could the issue here be that you're adding an item to the collection in the control's constructor, which the designer is then separately serializing?

I think your commented "should have been generated" code is also incorrect. Since the control's constructor adds InternalAnger to the collection, why would you also want to see it in the InitializeComponent?

Dan Puzey
That's not an issue, it should still serialize it correctly, I just put it there for convenience. Anyway, I've worked out whats wrong, but I don't know how to fix it. See my question for an edit.
Jules
A: 

Take the DesignerSerializationVisibility.Content off the InternalAnger property and it should work as you expect.

For example (C# but the idea is the same)

public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
        this.InternalAnger = new Anger();
        this.InternalAnger.SomeValue = 2;
        this.Angers.Add(this.InternalAnger);
    }

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public List<Anger> Angers
    {
        get { return this.list; }
    }

    public Anger InternalAnger { get; set; }

    private List<Anger> list = new List<Anger>();
    private Anger _InternalAnger;
}

public class Anger
{
    public Anger() { }

    public int SomeValue { get; set; }
}

generates:

WindowsFormsApplication1.Anger anger1 = new WindowsFormsApplication1.Anger();
anger1.SomeValue = 2;
this.userControl11.Angers.Add(anger1);
this.userControl11.InternalAnger = anger1;

which is, I think, what you want.

Grammarian
Hi, InternalAnger has to have the DesignerSerializationVisibility.Content attached because it's a ReadOnly property - otherwise your solution would work fine. I've worked out what is going on - see the OP for the edit.
Jules
A: 

As i said in the OP, the problem boils down to the name that I give to the internal property and the collection.

Without delving into a custom codedom serializer, the simple solution is to make sure the internal property's name is alphabetically before any other property that will reference it.

I do this by retaining the original property name 'InternalProperty', but I disable serialization and refer it to a proxy property, that is cunningly named, and is serialized.

Private _InternalProperty
Public ReadOnly Property InternalProperty
    Get
        Return Me._ProxyInternalProperty 
    End Get
End Property

<Browsable(False), EditorBrowsable(Never), DesignerSerializationVisibility(Content)> _
Public ReadOnly Property _ProxyInternalProperty
    Get
        Return Me._InternalProperty
    End Get
End Property

This is a hack, but its better than renaming my property AInternalProperty. Also, the user will never see _ProxyInternalProperty because it's hidden, and even if they did discover it, there is no danger in referencing it.

Jules