Enums zur Laufzeit erstellen/ändern
-
09-09-2019 - |
Frage
Ich erstelle ein Programm, in dem der Benutzer die Möglichkeit hat, seine eigenen benutzerdefinierten Eigenschaften zu erstellen, die letztendlich in einem angezeigt werden PropertyGrid
.Im Moment möchte ich mich nicht mit benutzerdefinierten Editoren herumschlagen, deshalb erlaube ich nur Eigenschaften primitiver Typen (string
, int
, double
, DateTime
, bool
usw.), dass die PropertyGrid
verfügt bereits über integrierte Editoren für.
Allerdings möchte ich dem Benutzer auch die Möglichkeit geben, Multiple-Choice-Eigenschaften zu erstellen, in denen er eine Liste möglicher Werte definieren kann, die wiederum als Dropdown-Liste im angezeigt werden PropertyGrid
.
Wenn ich eine fest codiere Enum
In meinem Code zeigt das Eigenschaftenraster automatisch die Eigenschaften davon an enum
als Dropdown-Liste.Aber kann ich zur Laufzeit eine Aufzählung erstellen und/oder ändern, damit der Benutzer eine weitere Eigenschaftsoption hinzufügen und zu dieser zurückkehren kann? PropertyGrid
und ihre neue Option in einem Dropdown-Menü sehen?
Aktualisieren
Wenn ich Patricks Kommentar bedenke, denke ich das Enum
s sind in diesem Fall nicht der richtige Weg.Wie kann ich stattdessen eine Liste von Zeichenfolgen verwenden, um ein Dropdown-Menü in einem zu füllen? PropertyGrid
Artikel?Wäre dafür ein benutzerdefinierter Editor erforderlich?
Lösung
Die Antwort liegt in einer einfachen Klasse: TypeConverter.(Und ja, Aufzählungen sind hier nicht geeignet).
Da ich nicht viele Details habe, gehe ich davon aus, dass Sie ein PropertyGrid haben, das durch die SelectedObject-Eigenschaft mit einer Zielinstanz „verknüpft“ ist, und dass Ihre Zielinstanz ICustomTypeDescriptor implementiert, sodass Sie Eigenschaften hinzufügen können (d. h.PropertyDescriptors) zur Laufzeit.Ich kenne Ihr Design nicht, aber wenn Sie es nicht so machen, empfehle ich Ihnen, es sich anzusehen.
Nehmen wir nun an, Sie fügen eine Zeichenfolgeneigenschaft hinzu und möchten, dass Ihr Benutzer eine Reihe von Einschränkungen für diese Eigenschaft angibt.Auf Ihrer Benutzeroberfläche kann der Benutzer eine Reihe von Zeichenfolgen eingeben und als Ergebnis erhalten Sie eine Liste mit Zeichenfolgen.Möglicherweise führen Sie in Ihrer Zielinstanz ein Wörterbuch mit Eigenschaften. Nehmen wir also an, dass diese neue Liste auch dort gespeichert ist.
Schreiben Sie jetzt einfach einen neuen Konverter, der von TypeConverter (oder in diesem Beispiel vielleicht von StringConverter) abgeleitet ist.Sie müssen GetStandardValuesSupported überschreiben, um true und zurückzugeben GetStandardValues um die Liste der Zeichenfolgen zurückzugeben (verwenden Sie den Kontextparameter, um auf die Instanzeigenschaft und ihre Liste der Zeichenfolgen zuzugreifen).Dieser Konverter wird von Ihrem PropertyDescriptor mit der Eigenschaft PropertyDescriptor.Converter veröffentlicht.
Ich hoffe, das ist nicht zu nebulös.Wenn Sie eine spezielle Frage zu diesem Prozess haben, lassen Sie es mich einfach wissen.
Andere Tipps
Die typische Engineering-Lösung für Ihr Problem ist die Liste als Referenzdaten in der Datenbank zu verwenden, zu erhalten. Im Allgemeinen Aufzählungen sollen Konstanten bei der Kompilierung definiert werden, und deren Änderung in späteren Code freigegeben wird (ganz zu schweigen von der Laufzeit) abgeraten, da es Nebenwirkungen in switch-Anweisungen führen kann.
könnten Sie erstellen Code Ihren Code verwenden, ist es dann in eine temporäre Textdatei speichern, und dann verwenden. Dies wäre langsam, da es sich um die Festplatte verwenden. Ich würde empfehlen, Reflexion .
Bearbeiten: Ich fand das perfekte Beispiel in einem meiner Bücher, hier ist es (es ist ziemlich lange, aber wenn Sie es in VS kopieren, wird es mehr Sinn machen)
.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);
}
}
}
Ausgabe:
Summe (2000) = 2.001.000
Looping. Abgelaufene Millisekunden:
11.468,75 für 1000000 Iterationen
Summe (2000) = 2.001.000
Rohe Gewalt. Abgelaufene Millisekunden:
406,25 für 1 Million Iterationen
Hier ist ein Link auf das gesamte Kapitel, wenn Sie möchten mehr info.
Sie können Enum.GetNames() und Enum.GetValues() verwenden, um Werte abzurufen und ihnen dynamisch neue hinzuzufügen.Ich empfehle Ihnen jedoch, eine Liste anstelle einer Aufzählung zu verwenden oder Ihr Design zu überdenken.etwas riecht nicht richtig.