views:

333

answers:

3

I'm extending the Gridview Web Control as my first attempt at creating a custom control.

As part of the extension I am encapsulating the localization of the grid column headers within the control. Among others, I'm exposing a couple of properties to enable this feature:

bool AutoLocalizeColumnHeaders - enables the feature

string HeaderResourceFile - identifies a strongly typed resource class from which to get the header text

I'm overriding the OnRowDataBound handler and getting the appropriate ResourceManager using Reflection to populate the header text. This is all working nicely but I would like to display a list of the available Strongly Typed Resource Classes in the Property window for the user to choose from, rather than them having to type in the name manually.

I've created a TypeConverter for displaying a dropdown in which to display the available classes but can't work out how to get the list of available class names to display?

I've been trying for quite a while now without success and am at the point of losing my sanity. I'm assuming that there must be some way to achieve this using Reflection?

A: 

Interesting idea. I imagine that your property is currently set as a string? i wonder if you set your property as an Enum, then create an Enum with the strongly typed resource classes, will it be smart enough to show those in the property window. it is smart enough to show it in the code behind, cant see why it couldnt load it in the properties window.

Victor
+1  A: 

I think I've found the solution now.

Running the following from inside a Page_Load event gave me the Resource class names:

String[] resourceClassNames = (from type in assembly.GetTypes()
   where type.IsClass && type.Namespace.Equals("Resources")
   select type.Name).ToArray();

So I thought I'd be able to do something similar from inside the GetResourceFileNames(ITypeDescriptorContext context) function of the TypeConverter, using the context parameter to get hold of the correct assembly. Unfortunately, I could only seem to get the Custom Control's assembly or the System.Web assembly.

So, instead I've created a UITypeEditor which has an IServiceProvider passed into the EditValue routine. From this I was able to create an instance of ITypeDiscoveryService which I then used to get all of the types from the correct assembly:

public override object EditValue(ITypeDescriptorContext context, 
                                 IServiceProvider provider, object value)
{
    // Check if all the expected parameters are here
    if (context == null || context.Instance == null || provider == null)
    {
        // returning with the received value
        return base.EditValue(context, provider, value);
    }

    // Create the Discovery Service which will find all of the available classes
    ITypeDiscoveryService discoveryService = (ITypeDiscoveryService)provider.GetService(typeof(ITypeDiscoveryService));
    // This service will handle the DropDown functionality in the Property Grid
    _wfes = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;

    // Create the DropDown control for displaying in the Properties Grid
    System.Windows.Forms.ListBox selectionControl = new System.Windows.Forms.ListBox();
    // Attach an eventhandler to close the list after an item has been selected
    selectionControl.SelectedIndexChanged += new EventHandler(selectionControl_SelectedIndexChanged);
    // Get all of the available types
    ICollection colTypes = discoveryService.GetTypes(typeof(object), true);
    // Enumerate the types and add the strongly typed
    // resource class names to the selectionControl
    foreach (Type t in colTypes)
    {
        if (t.IsClass && t.Namespace.Equals("Resources"))
        {
            selectionControl.Items.Add(t.Name);
        }
    }
    if (selectionControl.Items.Count == 0)
    {
        selectionControl.Items.Add("No Resources found");
    }
    // Display the UI editor combo
    _wfes.DropDownControl(selectionControl);

    // Return the new property value from the UI editor combo
    if (selectionControl.SelectedItem != null)
    {
        return selectionControl.SelectedItem.ToString();
    }
    else
    {
        return base.EditValue(context, provider, value);
    }
}

void selectionControl_SelectedIndexChanged(object sender, EventArgs e)
{
    _wfes.CloseDropDown();
}

This seems to work well although I expect there's a more stylish way to get the types required using LinQ, but I've only just started looking at LinQ and I can't seem to get the correct syntax when querying against a collection rather than an array as in the earlier example.

If anyone can suggest the LinQ syntax that would do this or indeed, a better way of accomplishing the whole thing, then it would be most welcome.

Graham Watson
A: 

In my solution posted previously, I wanted to find a way to do the following using LinQ:

    // Get all of the available types
    System.Collections.ICollection colTypes = discoveryService.GetTypes(typeof(object), true);
    // Enumerate the types and add the strongly typed resource class names to the selectionControl
    foreach (Type t in colTypes)
    {
        if (t.IsClass && t.Namespace.Equals("Resources"))
        {
            selectionControl.Items.Add(t.Name);
        }
    }

This seems to do the business:

    // Get all of the available class names from the Resources namespace
    var resourceClassNames = from Type t in discoveryService.GetTypes(typeof(object), true)
                        where t.IsClass && t.Namespace.Equals("Resources")
                        select t.Name;

    selectionControl.Items.AddRange(resourceClassNames.ToArray());

It certainly looks a lot cleaner and I would guess better performing as it's not looping through all of the available Types, checking for ones that meet the criteria in my code; although I suppose that LinQ would be doing this for me, albeit in a more efficient way?

Graham Watson