题
我正在创建一个程序,用户可以选择创建自己的自定义属性,这些属性最终将显示在 PropertyGrid
. 。现在我不想搞乱自定义编辑器,所以我只允许原始类型属性(string
, int
, double
, DateTime
, bool
等) PropertyGrid
已经内置了编辑器。
但是,我还想为用户提供创建多个选择属性的选项,他们可以在其中定义可能值的列表,这些值又将显示为下拉列表中的 PropertyGrid
.
当我硬编码 Enum
在我的代码中,属性网格自动显示该属性 enum
作为下拉列表。但是我可以在运行时创建和/或修改枚举,以便用户可以添加另一个属性选项,然后返回到 PropertyGrid
并在下拉菜单中看到他们的新选项?
更新
考虑到帕特里克的评论,我认为 Enum
在这种情况下,这不是正确的方法。那么我如何使用字符串列表来填充下拉列表 PropertyGrid
物品?这需要自定义编辑器吗?
解决方案
答案是一个简单的类:的的TypeConverter 即可。 (当然,枚举不适合这里)。
由于我没有很多的细节,我会认为你在SelectedObject财产和你的目标实例实现ICustomTypeDescriptor,这样就可以在运行时添加的属性(即的PropertyDescriptor)有一个PropertyGrid的“链接”的目标实例。我不知道你的设计,但如果你不是做这样的,我劝你还是看看吧。
现在让我们假设你添加一个字符串属性,并且您想让您的用户指定的一组约束该属性。你的UI让我们用户输入一组字符串,你会得到的字符串作为结果的列表。也许你在你的目标实例属性的字典,以便让我们假设这个新的列表存储在那里了。
现在,只写自TypeConverter(字符串转换或在此实例中也许)衍生的新的转换器。你将不得不重写GetStandardValuesSupported返回true和 GetStandardValues 返回字符串列表(使用上下文参数访问实例属性和它的字符串列表)。此转换器将通过与PropertyDescriptor.Converter财产您的PropertyDescriptor予以公布。
我希望这不是太含糊不清。如果你对这个过程中的具体问题,只是让我知道。
其他提示
在典型的工程解决问题的方法是使用以保持列表作为数据库中的参考数据。一般而言枚举旨在是其变形例在后面的发布的代码在编译时定义的常数,并且是不鼓励(更不用说运行时),因为它可以在switch语句中引起副作用。
您可以使用您的代码创建代码,然后将其保存到临时文本文件中,然后使用它。这会很慢,因为它涉及到使用 HDD。我建议调查一下 反射.
编辑: 我在我的一本书中找到了完美的例子,就在这里(它相当长,但如果你将它复制到 VS 中,它会更有意义)。
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);
}
}
}
输出:(2000) 总和 = 2001000
循环播放。经过的毫秒数:
11468.75(1000000 次迭代)
(2000) 总和 = 2001000
蛮力。经过的毫秒数:
1000000 次迭代为 406.25
这里 如果您想了解更多信息,可以找到整个章节的链接。
可以使用Enum.GetNames()和Enum.GetValues()中检索值和动态添加新的到它们。虽然我建议你使用一个列表,而不是枚举或重新考虑你的设计。东西不闻正确的。