2009-04-14 12 views
8

Sto creando un programma in cui l'utente ha la possibilità di creare le proprie proprietà personalizzate che verranno visualizzate in un PropertyGrid. In questo momento non voglio fare confusione con gli editor personalizzati, quindi permetto solo proprietà di tipo primitivo (string, int, double, DateTime, bool ecc.) Che lo strumento PropertyGrid ha già incorporato editor per.Creazione/modifica di enum in runtime

Tuttavia, desidero anche offrire all'utente la possibilità di creare proprietà a scelta multipla in cui è possibile definire un elenco di valori possibili che a loro volta verranno visualizzati come elenco a discesa in PropertyGrid.

Quando codice un Enum nel mio codice, la griglia di proprietà mostra automaticamente le proprietà di tale enum come un elenco a discesa. Ma posso creare o modificare un'enumerazione in fase di runtime in modo che l'utente possa aggiungere un'altra opzione di proprietà e tornare allo PropertyGrid e vedere la loro nuova opzione in un menu a discesa?

Aggiornamento

Considerando Patricks commento, mi viene da pensare che Enum s non sono la strada giusta da percorrere in questo caso. Quindi, invece, come posso utilizzare un elenco di stringhe per popolare un elenco a discesa in un elemento PropertyGrid? Richiederebbe un editor personalizzato?

risposta

5

La risposta è in una classe semplice: TypeConverter. (e sì, le enumerazioni non sono adatte qui).

Dato che non ho molti dettagli, supporrò che tu abbia un PropertyGrid "collegato" a un'istanza di destinazione dalla proprietà SelectedObject e che l'istanza di destinazione implementa ICustomTypeDescriptor in modo che tu possa aggiungere proprietà (ad esempio PropertyDescriptors) a runtime. Non conosco il tuo design, ma se non lo fai, ti consiglio di dargli un'occhiata.

Ora diciamo che si aggiunge una proprietà stringa e che si desidera consentire all'utente di specificare una serie di vincoli per questa proprietà. L'interfaccia utente consente all'utente di inserire un set di stringhe e di conseguenza ottenere un elenco di stringhe. Forse si mantiene un dizionario di proprietà nell'istanza di destinazione, quindi supponiamo che anche questo nuovo elenco sia memorizzato.

Ora, basta scrivere un nuovo convertitore derivato da TypeConverter (o StringConverter, forse in questo esempio). Dovrai sostituire GetStandardValuesSupported per restituire true e GetStandardValues ​​ per restituire l'elenco di stringhe (utilizzare il parametro context per accedere alla proprietà Instance e al relativo elenco di stringhe). Questo convertitore sarà pubblicato dal tuo PropertyDescriptor con la proprietà PropertyDescriptor.Converter.

Spero che questo non sia troppo nebuloso. Se hai una domanda specifica su questo processo, fammelo sapere.

+0

puoi fornire un codice di esempio per quella soluzione – Cracker

0

È possibile creare codice utilizzando il codice, quindi salvarlo in un file di testo temporaneo e quindi utilizzarlo. Questo sarebbe lento poiché comporta l'uso dell'HDD. Consiglierei di esaminare reflection.

Modifica: Ho trovato l'esempio perfetto in uno dei miei libri, eccolo qui (è piuttosto lungo, ma se lo copi in VS, avrà più senso).

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); 
     } 
    } 
} 

uscita: Somma di (2000) = 2001000
Looping. Trascorsi millisecondi:
11468,75 per 1000000 iterazioni
Somma di (2000) = 2001000
Brute Force. millisecondi trascorsi:
406,25 per milione iterazioni

Here è un link per l'intero capitolo, se volete maggiori informazioni.

+1

Odio davvero quando le persone in basso votano una risposta senza spiegare perché. Non funziona? Non risponde alla domanda? – Tarynn

-7

È possibile utilizzare Enum.GetNames() ed Enum.GetValues ​​() per recuperare i valori e aggiungerne di nuovi dinamicamente. anche se ti suggerisco di usare una lista invece di enum o ripensare il tuo design. qualcosa non ha un buon odore

+2

Uh, no, non puoi. – Samuel

+0

possiamo aggiungerli via riflessione? –

+0

GETNames e GETValues ​​dovrebbero darti un suggerimento che NON puoi cambiare enum con questi metodi ... –

3

La tipica soluzione tecnica per il tuo problema è quella di utilizzare per mantenere l'elenco come dati di riferimento nel database. In generale le enumerazioni sono intese come costanti definite in fase di compilazione e la loro modifica in un successivo rilascio di codice è scoraggiata (per non parlare del runtime), poiché può causare effetti collaterali nelle istruzioni switch.