Crear/modificar enumeraciones en tiempo de ejecución
-
09-09-2019 - |
Pregunta
Estoy creando un programa donde el usuario tiene la opción de crear sus propias propiedades personalizadas que finalmente se mostrarán en un PropertyGrid
.En este momento no quiero meterme con editores personalizados, así que solo permito propiedades de tipo primitivo (string
, int
, double
, DateTime
, bool
etc.) que el PropertyGrid
ya tiene editores integrados para.
Sin embargo, también quiero darle al usuario la opción de crear propiedades de opción múltiple donde pueda definir una lista de valores posibles que a su vez aparecerá como una lista desplegable en el PropertyGrid
.
Cuando codifico un Enum
en mi código, la cuadrícula de propiedades muestra automáticamente las propiedades de ese enum
como una lista desplegable.Pero, ¿puedo crear o modificar una enumeración en tiempo de ejecución para que el usuario pueda agregar otra opción de propiedad y volver a la PropertyGrid
¿Y ver su nueva opción en un menú desplegable?
Actualizar
Teniendo en cuenta el comentario de Patrick, estoy pensando que Enum
s no son el camino correcto a seguir en este caso.Entonces, ¿cómo puedo usar una lista de cadenas para completar un menú desplegable en un PropertyGrid
¿artículo?¿Eso requeriría un editor personalizado?
Solución
La respuesta está en una clase simple: Convertidor de tipos.(y sí, las enumeraciones no son adecuadas aquí).
Como no tengo muchos detalles, asumiré que tiene un PropertyGrid "vinculado" a una instancia de destino mediante la propiedad SelectedObject y que su instancia de destino implementa ICustomTypeDescriptor para que pueda agregar propiedades (es decir,PropertyDescriptors) en tiempo de ejecución.No conozco tu diseño pero si no te va así te aconsejo que le eches un vistazo.
Ahora digamos que agrega una propiedad de cadena y que desea permitir que su usuario especifique un conjunto de restricciones para esta propiedad.Su interfaz de usuario permite al usuario ingresar un conjunto de cadenas y, como resultado, obtiene una lista de cadenas.Tal vez mantenga un diccionario de propiedades en su instancia de destino, así que supongamos que esta nueva lista también está almacenada allí.
Ahora, simplemente escriba un nuevo convertidor derivado de TypeConverter (o quizás StringConverter en este ejemplo).Tendrá que anular GetStandardValuesSupported para devolver verdadero y Obtener valores estándar para devolver la lista de cadenas (use el parámetro de contexto para acceder a la propiedad Instancia y su lista de cadenas).Este convertidor será publicado por su PropertyDescriptor con la propiedad PropertyDescriptor.Converter.
Espero que esto no sea demasiado confuso.Si tiene una pregunta específica sobre este proceso, hágamelo saber.
Otros consejos
La solución de ingeniería típica a su problema es utilizar para mantener la lista como datos de referencia en su base de datos. En las enumeraciones generales pretenden ser constantes definidas en tiempo de compilación, y su modificación en posteriormente liberado de código no se recomienda (por no hablar de tiempo de ejecución), ya que puede causar efectos secundarios en los estados de conmutación.
Se puede crear código con el código, a continuación, guardarlo en un archivo de texto temporal, y luego usarlo. Esto sería lento, ya que implica el uso del disco duro. Yo recomendaría mirar en reflexión .
Editar: He encontrado el perfecto ejemplo de uno de mis libros, aquí está (es bastante largo, pero si lo copia en VS, que va a hacer más sentido)
.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);
}
}
}
Salida:
Suma de (2000) = 2.001.000
Looping. milisegundos transcurridos: Read
11.468,75 para 1000000 iteraciones
Suma de (2000) = 2.001.000
Fuerza bruta. milisegundos transcurridos: Read
406.25 para 1000000 iteraciones
Aquí es un enlace a todo el capítulo, si desea obtener más información.
Puede utilizar Enum.GetNames() y Enum.GetValues() para recuperar valores y agregarles dinámicamente otros nuevos.aunque te sugiero que uses una lista en lugar de una enumeración o replantees tu diseño.algo no huele bien.