Criando / Enums Modificando em tempo de execução
-
09-09-2019 - |
Pergunta
Estou criando um programa onde o usuário tem a opção de criar suas próprias propriedades personalizadas que acabará por ser exibidos em uma PropertyGrid
. Agora eu não quero mexer com editores personalizados, então eu só estou permitindo propriedades primitivas tipo (string
, int
, double
, DateTime
, bool
etc.) que o PropertyGrid
já construídas em editores de.
No entanto, eu também quero dar ao usuário a opção de criar várias propriedades de escolha onde podem definiu uma lista de possíveis valores que por sua vez vai aparecer como uma lista suspensa na PropertyGrid
para baixo.
Quando eu disco rígido código um Enum
no meu código a grade de propriedades mostra automaticamente propriedades dessa enum
como uma lista suspensa. Mas eu posso criar e ou modificar uma enumeração em tempo de execução para que o usuário pode adicionar uma outra opção de propriedade, e voltar para o PropertyGrid
e ver sua nova opção em um drop-down?
Atualizar
Considerando Patricks comentário, eu estou pensando que Enum
s não é o caminho certo a seguir neste caso. Então, em vez como eu posso usar uma lista de strings para preencher um drop-down em um item de PropertyGrid
? Oxalá exigem um editor personalizado?
Solução
A resposta está em uma classe simples: TypeConverter . (E sim, enums não são adequados aqui).
Uma vez que eu não tenho um monte de detalhes, vou assumir que você tem um PropertyGrid "ligada" a uma instância de destino pela propriedade SelectedObject e que seu alvo exemplo implementa ICustomTypeDescriptor de modo que você pode adicionar propriedades (ou seja PropertyDescriptors) em tempo de execução . Eu não sei o seu design, mas se você não está fazendo assim, eu aconselho você a ter um olhar para ele.
Agora, vamos dizer que você adicionar uma propriedade string e que deseja deixar o seu usuário especificar um conjunto de restrições para esta propriedade. Seu let UI é o usuário digite um conjunto de cordas e você terá uma lista de strings como resultado. Talvez você manter um dicionário de propriedades na sua instância de destino então vamos assumir essa nova lista é armazenada lá também.
Agora, basta escrever um novo conversor derivado de TypeConverter (ou StringConverter talvez neste exemplo). Você terá que substituir GetStandardValuesSupported para retornar verdadeiro e GetStandardValues ?? para retornar a lista de strings (use o parâmetro de contexto para acessar a propriedade Instância e sua lista de strings). Este conversor será publicado pelo seu PropertyDescriptor com a propriedade PropertyDescriptor.Converter.
Espero que isso não é muito nebuloso. Se você tem uma pergunta específica sobre esse processo, é só me avisar.
Outras dicas
A solução de engenharia típica para o seu problema é a utilização para manter a lista de dados de referência em seu banco de dados. Em enums gerais se destinam a ser constantes definidas em tempo de compilação, e sua modificação mais tarde lançada do código é desencorajado (muito menos tempo de execução), pois pode causar efeitos secundários em declarações switch.
Você pode criar código usando seu código, em seguida, salve-o em um arquivo de texto temporário e, em seguida, usá-lo. Este seria lenta, uma vez que envolve o uso do HDD. Eu recomendaria para reflexão .
Editar: Eu encontrei o exemplo perfeito de um dos meus livros, aqui está (é bastante longo, mas se você copiá-lo em VS, ele vai fazer mais 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);
}
}
}
Saída:
Soma de (2000) = 2.001.000
Looping. milissegundos decorridos:
11.468,75 para 1.000.000 iterações
Soma de (2000) = 2.001.000
Brute Force. milissegundos decorridos:
406,25 para 1000000 iterações
Aqui é um link para o capítulo inteiro se você gostaria de mais informações.
Você pode usar Enum.GetNames () e Enum.GetValues ??() para valores retreive e dinamicamente adicionar novas para eles. embora eu sugiro que você use uma lista em vez de enum ou repensar o seu design. Algo não cheira bem.