Creating a Collection property in a user control

Posted by jblitzie on 14-Jul-2011 13:39

10.2B04

Is it possible yet to create a collection property in a user control and have it available in the designer when that control is dropped on a form? I seem to be able to get it to almost work.

Depending on the type of collection (Generics), I can sometimes get the property to display with a button to edit the collection's items, but either the Add button is disabled, or I can't edit the item (System.Object) once added. (Image1, Image2)

I've tried a number of collection types and signatures but cannot seem to come up with a combination that works.

"System.Collections.Generic.Dictionary<TKey, TValue>"

"System.Collections.Generic.KeyValuePair<TKey, TValue>"

"System.Collections.Generic.SortedList<TKey, TValue>".

etc.

At the moment, all I'm trying to accomplish is a simple collection of text strings that can be added via the designer. I've had TKey and TValue set to System.String and/or System.Object with no luck.

Thanks,

Jim

All Replies

Posted by Admin on 14-Jul-2011 15:16

Do you need a collection with a key? That would be two elements, one for the key, one for the value.

Did you try a generic list of System.String?

That's only a single object per list member. Should work better.

Posted by jblitzie on 14-Jul-2011 15:27

Hi Mike,

No, I this case I don't necessarily need a key. I did try this earlier (wasn't in my post)...

DEFINE PUBLIC PROPERTY ColTest AS "System.Collections.Generic.List<System.String>" NO-UNDO GET. SET.

When the items dialog displayed and I select Add, I get an error (see attached). (The image button in these posts are disabled but the video button is not)

Jim

Posted by Admin on 14-Jul-2011 15:36

Ah, I see.

The System.String does not have a constructor without any parameter But the code generator cannot generate code for the constructor parameter. You'll probably have to create a "holder class" in C# (or VB.NET if you have to) and use a generic list of that type.

That holder class should have a constructor with no parameter (default constructor) and a System.String property to accept your text.

Or you'll find an existing .NEZ type that you can use. But not an primitive type like System.String.

Posted by Matt Baker on 14-Jul-2011 15:39

Integer works fine, and I'm guessing the other primitive types do as well:

    define public property ColTest as "System.Collections.Generic.List" no-undo
    get.
    set.

The exception is coming from the .NET designer code.  I just tried in visual studio 2010 and it generates the exact same error if you use a list of strings.

You'll need to write your own designer class to handle it as it appears the MS default collection designer cannot handle it.

mattB

Posted by Admin on 14-Jul-2011 15:51

Yes, once the Collection is shown in the property grid it should cause the same issues in the Visual Studio (well done Matt et al.).

The simple holder class written in C# should work without the need to define a specialized Desinger type, as we are using that in our framework.

Posted by jblitzie on 15-Jul-2011 07:47

Thanks guys for digging into this.

Mike,

Can you elaborate on a 'holder' class?

Thanks,

Jim

Posted by Admin on 15-Jul-2011 08:34

Basically just a simple C# class with a Single property of type System.String.

Posted by jblitzie on 15-Jul-2011 09:49

Ok, I think I got it.

I'll give that a try.

Thanks!

Posted by jblitzie on 15-Jul-2011 13:54

Mike,

I'm wondering if I'm just not understanding exactly how to do this.

I created a class in C# that contains a single property:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace Activant.StringHolder

{

    public class StringHolder

    {

        public string holderString

        {

            get { return holderString; }

            set { holderString = value; }

        }

    }

}

I built the VS project and copied the dll into my OEA project. I added the generic property to my UserControl:

USING Activant.StringHolder.* FROM ASSEMBLY.

DEFINE PUBLIC PROPERTY ColTest AS "System.Collections.Generic.List<StringHolder>" NO-UNDO GET. SET.

After saving the UserControl, I added it to my form in the Visual Designer. The ColTest property was available. When I selected the collection button, the dialog displayed properly (Image 4).

I selected the Add button and received the error shown in Image 5.

I also tried creating the holder class without the get and set.

public class StringHolder

{

    public string holderString;

}

After going through the same steps above, when I clicked Add it created an item, but I couldn't edit the value (Image 6).

In addition, after selecting OK and saving the form, the item that was added was not written to InitializeComponent in the form class. When I selected the collection button again, there were no items.

Regards,

Jim

Posted by Admin on 15-Jul-2011 15:39

Ok, just reviewed our solution a second time.

What's probably missing on your end is the

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]

attribute on the List property in the UserControl. We are setting that using using our own ICustomTypeDescriptor implementation. This is - to my knowledge - the only way of adding attributes like the DesignerSerializationVisibilityAttribute in the OpenEdge Architect Visual Designer. When implementing this Interface in a Control or Component the design time instance itself becomes responsible to return (at design runtime) all information about how to handle the properties of the class etc.. Using that way you can "inject" additional attributes.

This is what I get with our way of injecting the DesignerSerializationVisibilityAttribute:

        /*  */

        /* testUserControl1 */

        /*  */

        THIS-OBJECT:testUserControl1:BackColor = System.Drawing.Color:Red.

        THIS-OBJECT:testUserControl1:Location = NEW System.Drawing.Point(117, 79).

        THIS-OBJECT:testUserControl1:Name = "testUserControl1".

        THIS-OBJECT:testUserControl1:Size = NEW System.Drawing.Size(124, 139).

        stringHolder1:Value = "aaa".

        stringHolder2:Value = "bbb".

        THIS-OBJECT:testUserControl1:StringList:Add(stringHolder1).

        THIS-OBJECT:testUserControl1:StringList:Add(stringHolder2).

        THIS-OBJECT:testUserControl1:TabIndex = 0.

Our C# class basically looks like yours. My String property is called "Value" and I have added an empty default constructor. But I doubt that is relevant.

A few resources to look up for you:

http://msdn.microsoft.com/en-us/library/system.componentmodel.icustomtypedescriptor.aspx

http://blog.consultingwerk.de/consultingwerkblog/2011/06/presentation-download-for-customizing-the-openedge-architect-visual-designer/

A while back we've made a strategic decision to not make that ICustomTypeDescriptor implementation open source because it's one of the key building blocks of our SmartComponent Libraries design time functionality.

I know that Progress has this feature (ability to specify the .NET property attributes in a simpler way) on the list of future enhancements. I doubt it's high on the list though.

A way to work around this as well would be to import Progress.NetUI.dll as an Assembly reference into that same C# project and create the user control with the property there. There you can use the simple .NET way of adding the attribute.

Nachricht geändert durch Mike Fechner

Posted by jblitzie on 19-Jul-2011 14:12

Ok, I have something working (sort of).

I created a class in C# that is just a holder for a System.String property. One difference from my earlier attempt is the [Serializable] attribute.

using System;
using System.Linq;
using System.Text;

namespace Activant.StringHolder
{
   
[Serializable]
    public class MyString
    {
        private string _value;
        public string Value
        {
            get
            {
                return _value;
            }
            set
            {
                _value = value;
            }
        }

        public MyString()
        {
        }
    }

}

After building the dll and bringing it into OEA, I added the following to my user control:

DEFINE PUBLIC PROPERTY ColTest AS "System.Collections.Generic.List" NO-UNDO GET. SET.

and in the Constructor, after InitializeComponent:

ColTest = NEW "System.Collections.Generic.List"().

When the User Control is added to my Form, there is a Property named ColTest in the Properties tab with a Collections button. After selecting it, adding some items, and selecting OK, the section for the UserControl in InitializeComponent looks like this:

/*  */
/* collectionProperty1 */
/*  */
THIS-OBJECT:collectionProperty1:ColTest = CAST(resources:GetObject("collectionProperty1.ColTest"), "System.Collections.Generic.List").
THIS-OBJECT:collectionProperty1:Location = NEW System.Drawing.Point(73, 40).
THIS-OBJECT:collectionProperty1:Name = "collectionProperty1".
THIS-OBJECT:collectionProperty1:Size = NEW System.Drawing.Size(234, 222).
THIS-OBJECT:collectionProperty1:TabIndex = 0.

The collection's item values are written to the resx file and persist.

This is not too bad of a workaround and it at least gives me the property grid for simple lists at design time. I should be able to extend this to allow the user of a keyed collection such as SortedList.

Please feel free to comment on the solution.

Jim

Posted by Admin on 19-Jul-2011 14:21

The collection's item values are written to the resx file and persist.

 

If I'm not mistaking, the collection items are binary serialized to the resx file that way, right?

I've came across the advice to not use that, when investigating about these topics on the web. The binary serialization is dependent on all the Assembly attributes like name, version, vendor key and language. If one of those changes, the binary serialization becomes worthless.

From what you can influence, you shouldn't change the version of your assembly etc.. Not necessarily a recommended approach.

Posted by jblitzie on 19-Jul-2011 15:47

Hmmm. Yes, you are correct.

   

        AAEAAAD/////AQAAAAAAAAAMAgAAAENTdHJpbmdIb2xkZXIsIFZlcnNpb249MS4wLjAuMCwgQ3VsdHVy

        ZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1udWxsBAEAAACIAVN5c3RlbS5Db2xsZWN0aW9ucy5HZW5l

        cmljLkxpc3RgMVtbQWN0aXZhbnQuU3RyaW5nSG9sZGVyLk15U3RyaW5nLCBTdHJpbmdIb2xkZXIsIFZl

        cnNpb249MS4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1udWxsXV0DAAAABl9p

        dGVtcwVfc2l6ZQhfdmVyc2lvbgQAACBBY3RpdmFudC5TdHJpbmdIb2xkZXIuTXlTdHJpbmdbXQIAAAAI

        CAkDAAAAAQAAAAQAAAAHAwAAAAABAAAABAAAAAQeQWN0aXZhbnQuU3RyaW5nSG9sZGVyLk15U3RyaW5n

        AgAAAAkEAAAADQMFBAAAAB5BY3RpdmFudC5TdHJpbmdIb2xkZXIuTXlTdHJpbmcBAAAABl92YWx1ZQEC

        AAAABgUAAAAEdGVzdAs=

 

Any suggestions outside of creating the User Control in C#. Not sure I want to (or can, for other reasons) go there.

Posted by Admin on 19-Jul-2011 15:56

Any suggestions outside of creating the User Control in C#. Not sure I want to (or can, for other reasons) go there.

None that I haven't mentioned before. ICustomTypeDescriptor is my favorite. And it's actually a great way of getting to understand .NET internals.

Of course you might purchase a framework that supports this... (sorry, couldn't resist). Or parts of it.

Posted by jblitzie on 20-Jul-2011 14:48

Well, there obviously needs to be a lot more going on in the c# class than I'm able to figure out.

I imported the Progress dll to my C# class and created a user control. In it I added a generic collection of my string, added the c# dll to OEA and set my Progress user control to inherit from the C# base. The property showed up as expected but still serialized to the resx.

Unfortunately I've already spent way too long trying to figure this out and need to move on. Maybe at some point there will be a Progress solution.

Regards,

Jim

Posted by Admin on 20-Jul-2011 14:52

Did you declare the C# property with

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]

That is what changes the serialization style.

Posted by jblitzie on 20-Jul-2011 15:05

Tried with and without. Attached is the current C# class. It's very possible I'm not doing something correctly.

[View:~/cfs-file.ashx/__key/communityserver-discussions-components-files/19/DemoBaseControl.cs.zip:550:0]

I also had [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] on the collection. With that, I got a reference to the resx for every text value I created.

Posted by Admin on 20-Jul-2011 15:13

The DemoList Property needs the DesignerSerializationVisibility attribute, not the Value property of the MyString class:

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]

        public System.Collections.Generic.List DemoList

        {

            get

            {

                return _DemoList;

            }

            set

            {

                _DemoList = value;

            }

        }

And there is no need for implementing ICustomTypeDescriptor for this here in C# because the C# compiler can feed the normal TypeDescriptor class using the property attributes. The above should work.
Also there shouldn't be a need for the Serializable attribute on the MyString class. I'd remove it.

Posted by jblitzie on 20-Jul-2011 16:41

Mike,

That was it. In the many iterations of code changes I never had this exact arrangement.

Thanks a bunch for the help!

Jim

Posted by Admin on 20-Jul-2011 16:56

Glad it works for you now!

I guess you should get your name on the list of requesters for ABL support for property attributes (annotations). This was discussed during the ABL InfoExchange session during PUG Challenge Americas (and a couple of times before).

This thread is closed