views:

973

answers:

4

I'm creating a program where the user has the option of creating their own custom properties that will ultimately be displayed in a PropertyGrid. Right now I don't want to mess with custom editors, so I'm only allowing primitive type properties (string, int, double, datetime, bool etc.) that the PropertyGrid already has built in editors for.

However, I also want to give the user the option to create multiple choice properties where they can defined a list of possible values which in turn will show up as a drop down list in the PropertyGrid.

When I hard code an Enum in my code the property grid automatically shows properties of that enum as a drop down list. But can I create and or modify an enumeration at runtime so the user could add another property option, and go back to the propertygrid and see their new option in a drop down?

Update

Considering Patricks comment, I'm thinking that ENUMS are not the right way to go in this case. So instead how can I use a list of strings to populate a drop down in a property grid item? Would that require a custom editor?

Eric

A: 

You could create code using your code, then save it to a temporary text file, and then use it. This would be slow as it involves using the HDD. I would recommend looking into reflection.

Edit: I found the perfect example in one of my books, here it is (it's pretty lengthy but if you copy it into VS, it'll make more sense).

namespace Programming_CSharp
{
   using System;
   using System.Diagnostics;
   using System.IO;
   using System.Reflection;
   using System.Reflection.Emit;
   using System.Threading;

   // used to benchmark the looping approach
   public class MyMath
   {
      // sum numbers with a loop
      public int DoSumLooping(int initialVal)
      {
         int result = 0;
         for(int i = 1;i <=initialVal;i++)
         {
            result += i;
         }
         return result;
      }
   }

   // declare the interface
   public interface IComputer
   {
      int ComputeSum(  );
   }

   public class ReflectionTest
   {
      // the private method which emits the assembly
      // using op codes
      private Assembly EmitAssembly(int theValue)
      {
         // Create an assembly name
         AssemblyName assemblyName = 
            new AssemblyName(  );
         assemblyName.Name = "DoSumAssembly";

         // Create a new assembly with one module
         AssemblyBuilder newAssembly =
            Thread.GetDomain(  ).DefineDynamicAssembly(
            assemblyName, AssemblyBuilderAccess.Run);
         ModuleBuilder newModule =
            newAssembly.DefineDynamicModule("Sum");

         //  Define a public class named "BruteForceSums " 
         //  in the assembly.
         TypeBuilder myType =
            newModule.DefineType(
            "BruteForceSums", TypeAttributes.Public);

         // Mark the class as implementing IComputer.
         myType.AddInterfaceImplementation(
            typeof(IComputer));

         // Define a method on the type to call. Pass an
         // array that defines the types of the parameters,
         // the type of the return type, the name of the 
         // method, and the method attributes.
         Type[] paramTypes = new Type[0];
         Type returnType = typeof(int);
         MethodBuilder simpleMethod =
            myType.DefineMethod(
            "ComputeSum",
            MethodAttributes.Public | 
            MethodAttributes.Virtual,
            returnType,
            paramTypes);

         // Get an ILGenerator. This is used
         // to emit the IL that you want.
         ILGenerator generator = 
            simpleMethod.GetILGenerator(  );

         // Emit the IL that you'd get if you 
         // compiled the code example 
         // and then ran ILDasm on the output.

         // Push zero onto the stack. For each 'i' 
         // less than 'theValue', 
         // push 'i' onto the stack as a constant
         // add the two values at the top of the stack.
         // The sum is left on the stack.
         generator.Emit(OpCodes.Ldc_I4, 0);
         for (int i = 1; i <= theValue;i++)
         {
            generator.Emit(OpCodes.Ldc_I4, i);
            generator.Emit(OpCodes.Add);

         }

         // return the value
         generator.Emit(OpCodes.Ret);

         //Encapsulate information about the method and
         //provide access to the method's metadata
         MethodInfo computeSumInfo =
            typeof(IComputer).GetMethod("ComputeSum");

         // specify the method implementation.
         // Pass in the MethodBuilder that was returned 
         // by calling DefineMethod and the methodInfo 
         // just created
         myType.DefineMethodOverride(simpleMethod, computeSumInfo);

         // Create the type.
         myType.CreateType(  );
         return newAssembly;
      }

      // check if the interface is null
      // if so, call Setup.
      public double DoSum(int theValue)
      {
         if (theComputer == null)
         {
            GenerateCode(theValue);
         }

         // call the method through the interface
         return (theComputer.ComputeSum(  ));
      }

      // emit the assembly, create an instance 
      // and get the interface
      public void GenerateCode(int theValue)
      {
         Assembly theAssembly = EmitAssembly(theValue);
         theComputer = (IComputer) 
            theAssembly.CreateInstance("BruteForceSums");
      }

      // private member data
      IComputer theComputer = null;

   }

   public class TestDriver
   {
      public static void Main(  )
      {
         const int val = 2000;  // Note 2,000

         // 1 million iterations!
         const int iterations = 1000000;
         double result = 0;

         // run the benchmark
         MyMath m = new MyMath(  ); 
         DateTime startTime = DateTime.Now;            
         for (int i = 0;i < iterations;i++)
            result = m.DoSumLooping(val);
         }
         TimeSpan elapsed = 
            DateTime.Now - startTime;
         Console.WriteLine(
            "Sum of ({0}) = {1}",val, result);
         Console.WriteLine(
            "Looping. Elapsed milliseconds: " + 
            elapsed.TotalMilliseconds + 
            " for {0} iterations", iterations);

         // run our reflection alternative
         ReflectionTest t = new ReflectionTest(  );

         startTime = DateTime.Now; 
         for (int i = 0;i < iterations;i++)
         {
            result = t.DoSum(val);
         }

         elapsed = DateTime.Now - startTime;
         Console.WriteLine(
            "Sum of ({0}) = {1}",val, result);
         Console.WriteLine(
            "Brute Force. Elapsed milliseconds: " + 
            elapsed.TotalMilliseconds  + 
            " for {0} iterations", iterations);
      }
   }
}

Output: Sum of (2000) = 2001000
Looping. Elapsed milliseconds:
11468.75 for 1000000 iterations
Sum of (2000) = 2001000
Brute Force. Elapsed milliseconds:
406.25 for 1000000 iterations

Here is a link to the entire chapter if you would like more info.

Lucas McCoy
A: 

You can use Enum.GetNames() and Enum.GetValues() to retreive values and dynamically add new ones to them. although I suggest you to use a list instead of enum or rethink your design. something does not smell right.

CodeToGlory
Uh, no you can't.
Samuel
can we add them via reflection?
CodeToGlory
+3  A: 

The typical engineering solution to your problem is to use to maintain the list as reference data in your database. In general enums are intended to be constants defined at compile time, and their modification in later released of code is discouraged (let alone runtime), as it can cause side effects in switch statements.

+1  A: 

The answer is in a simple class: TypeConverter. (and yes, enums are not suitable here).

Since I have not a lot of details, I will assume that you have a PropertyGrid "linked" to a target instance by the SelectedObject property and that your target instance implements ICustomTypeDescriptor so that you can add properties (i.e. PropertyDescriptors) at runtime. I don't know your design but if you are not doing like this, I advise you to have a look at it.

Now let's say that you add a string property and that you want to let your user specify a set of constraints for this property. Your UI let's the user enter a set of strings and you get a list of strings as a result. Maybe you keep a dictionary of properties in your target instance so let's assume this new list is stored there too.

Now, just write a new converter derived from TypeConverter (or StringConverter maybe in this example). You will have to override GetStandardValuesSupported to return true and GetStandardValues to return the list of strings (user the context parameter to access the Instance property and its list of strings). This converter will be published by your PropertyDescriptor with the PropertyDescriptor.Converter property.

I hope this is not too nebulous. If you have a specific question on this process, just let me know.

Nicolas Cadilhac