StringCollectionEditor


Anton asked about design-time editing for the Words property (a StringCollection) of my ImageHipChallenge control.  It’s a good question.

I actually intended to adorn the property with [Browsable(false)] so that the property wouldn’t show up in the PropertyGrid in the designer (my original intent for this property was that in code you would open up a large dictionary file of some kind and load all of the words into collection).  But I forgot to add the attribute; oops.  The problem with adding words at design time is that you’ll most likely only enter a limited set of words (since you’re typing them in manually), and if the control has a small vocabulary, attacking it gets much easier for an attacker; she can visit the site manually until she knows most or all of the words your control uses, and then she can launch a programmatic attack using those discovered words.  If you didn’t want to use a dictionary file, you could also generate a random word and store it into the Words collection, or you could create your own derived control and override the ChooseWord method (or just change the source for ImageHipChallenge appropriately).

However, if you still want to use Words from the designer, it’s definitely possible, and only requires adding two attributes to the property:

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    [Editor(“System.Windows.Forms.Design.StringCollectionEditor, System.Design”,
        “System.Drawing.Design.UITypeEditor, System.Drawing”)]

The DesignerSerializationVisibilityAttribute tells the designer to add initialization code for the instance of the control for all values you add to the collection.  Thus, if you add three words to the collection, you’ll see something like the following in your initialization method

    this.TheControl.Words.AddRange(new string[] {“word1”, “word2”, “word3”});

The second attribute tells the designer to display the StringCollectionEditor when the ellipses for the property is clicked.  This editor allows you to enter one string value per line, and these are the values that will be serialized out to the AddRange method as shown above.

That works great when editing a property in the Visual Studio designer (and ends the answer to Anton’s question).  However, PropertyGrid is one of my favorite components to use in my own applications as it makes writing fairly intuitive user interfaces very straightforward (and more importantly, quick!) The problem is that the above solution as described doesn’t work when you bind your own PropertyGrid to an object that has a StringCollection property: attempting to edit the property by clicking the ellipses doesn’t display an instance of the StringCollectionEditor, but instead displays a default CollectionEditor (and attempting to click the “Add” button in this editor will result in an error message “Constructor on type System.String not found.”)

What’s the problem here?  Whenever you run into a situation where you’re specifying a type/assembly name and something isn’t working the way you think it should (for example, specifying the type name of one collection editor and ending up with a different one), one place to look is fuslogvw.  The Assembly Binding Log Viewer utility, part of the .NET Framework SDK, allows you to see problems when fusion attempts to load an assembly and fails (it actually allows you to see more than this with a few registry key modifications).  Run fuslogvw.exe and click the “Log Failures” checkbox. Then, try running a WinForms application that binds a PropertyGrid to a class that has a public StringCollection property adorned with the EditorAttribute described above.  Hit Refresh in fuslogvw.  Sure enough, you should see an item in the listview for System.Drawing, indicating that fusion was unable to load that assembly.  Double-click on the application name to see why:

    *** Assembly Binder Log Entry  (10/12/2004 @ 11:35:37 AM) ***
    The operation failed.Bind result: hr = 0x80070002. The system cannot find the file specified.
    Assembly manager loaded from:  C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\fusion.dll
    Running under executable  C:\Documents and Settings\…\WindowsApplication1\bin\Debug\WindowsApplication1.exe— A detailed error log follows.
    === Pre-bind state information ===
    LOG: DisplayName = System.Drawing (Partial)
    LOG: Appbase = C:\Documents and Settings\…\WindowsApplication1\bin\Debug\
    LOG: Initial PrivatePath = NULL
    LOG: Dynamic Base = NULL
    LOG: Cache Base = NULL
    LOG: AppName = NULL
    Calling assembly : System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.
    ===

    LOG: Processing DEVPATH.
    LOG: DEVPATH is not set. Falling through to regular bind.
    LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
    LOG: Post-policy reference: System.Drawing
    LOG: Attempting download of new URL C:/Documents and Settings/…/Debug/System.Drawing.DLL.
    LOG: Attempting download of new URL C:/Documents and Settings/…/Debug/System.Drawing/System.Drawing.DLL.
    LOG: Attempting download of new URL C:/Documents and Settings/…/Debug/System.Drawing.EXE.
    LOG: Attempting download of new URL C:/Documents and Settings/…/Debug/System.Drawing/System.Drawing.EXE.
    LOG: All probing URLs attempted and failed.

The problem is that the above EditorAttribute isn’t specific enough and only specifies partial names for the assemblies.  The simple fix is to change the EditorAttribute as follows:

    [Editor(
        “System.Windows.Forms.Design.StringCollectionEditor, System.Design, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a”,
        “System.Drawing.Design.UITypeEditor, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a”)]

Here, the designation for the System.Design and System.Drawing assemblies are full names rather than partial names.  Voila!  Fusion will find the assemblies in the GAC, and the String Collection Editor will work correctly in your own applications (note that the versions specified above are from the .NET Framework v1.1).

Comments (14)

  1. Anton Spass says:

    Stephen,

    thank you for the comprehensive answer!

    Special thanks for going deeper into StringCollectionEditor snags, your advice is of a great help.

  2. Stephen,

    Thanks for the great code and good techniques, however I’m not able to get the controls to render the image in my solution.

    I’m not getting any errors.

    Here’s my hipchallange dictionary collection, per your suggestion. VB Codebehind – Page Load.

    <code>

    ‘create a collection of words to used with my random number generator

    Dim objReader As New StreamReader(Server.MapPath("WORD.LST"))

    Dim sLine As String = ""

    Dim arrText As New ArrayList

    Dim intItem As Integer

    Try

    Do

    sLine = objReader.ReadLine()

    If Not sLine Is Nothing Then

    arrText.Add(sLine)

    End If

    Loop Until sLine Is Nothing

    Catch ex As Exception

    Finally

    objReader.Close()

    ‘added just for some scrabble scrambling (hee hee)

    arrText.Sort()

    arrText.Reverse()

    ‘my random number generator not shown here for security reasons

    ‘any random number generator will work

    intWords = GetRandomNumber(0, 1024)

    ‘read a word randomly chosen from the array of 1024 words

    ‘save the indexItem to a string

    Dim strItem As String = arrText(intWords).ToString()

    ‘add the string (word) to the hipchallanger collection

    ImageHipChallenge1.Words.Add(strItem)

    End Try

    </code>

    The arrText collection was tested by placing the strItem in a Label control. It works great, however the ImageHipChallenge is not rendering the image on the client.

    If you could post the code:

    "Figure 6 shows the ImageHipChallenge and HipValidator controls incorporated into the Personal Web Site Starter Kit that is included with the Visual Web Developer 2005 Express Edition Beta."

    I would very much appreciate it.

    Thanks ahead.

  3. I figured out the problem. Here’s my error: System.ArgumentException: Cannot find font ‘Stencil’

    My box didn’t have the stencil font. I downloaded and install a generic one. I could have just chosen another font. I will try different fonts as an experiment.

    Thanks again for this very cool code. 🙂

  4. Stephen Toub says:

    Great, glad you got it working!

  5. BioSlayer says:

    Here it is for .Net 2.0

    [Editor(

    "System.Windows.Forms.Design.StringCollectionEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",

    "System.Drawing.Design.UITypeEditor,System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

    )]

  6. Bino Joseph says:

    hi,

    am having a problem with collection. Its in web application. I have a property collection for a custom dropdownlist. But when ever the data is entered, its not saved.

    code

    //property

    [Category("CascadeDropdown"), Description("Collection of observers that listen to the this control. Populate this property through server code")]

           [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]

           public ObserverDropDownListCollection Observers

           {

               get { return this._observers; }

               set { this._observers = value; }

           }

    where

    ObserverDropDownListCollection : System.Collections.CollectionBase

    thanks in advance

    bino joseph

    bino291@yahoo.com

  7. lcdlcd says:

    Excellent browsing have the to

  8. Magiel van Gaalen says:

    Great explination. Could you provide me one for FrameWorks 3.5  Thanks in advance

  9. james says:

    solution is too precise and good… it helped me to open stringcollectioneditor with two lines of code