إنشاء / تعديل Enums في وقت التشغيل
-
09-09-2019 - |
سؤال
أنا إنشاء البرنامج حيث أن المستخدم لديه خيار إنشاء الخاصة بهم الخصائص المخصصة التي من شأنها في نهاية المطاف أن يتم عرضها في PropertyGrid
.الآن لا تريد الفوضى مع العرف المحررين ، لذلك أنا فقط السماح نوع بدائي خصائص (string
, int
, double
, DateTime
, bool
.... الخ) أن PropertyGrid
بالفعل قد بنيت في المحررين.
ولكن أريد أيضا أن تعطي المستخدم خيار إنشاء الاختيار من متعدد الخصائص حيث يمكن تعريف قائمة من القيم الممكنة والتي بدورها سوف تظهر قائمة منسدلة في PropertyGrid
.
عندما كنت بجد المدونة Enum
في بلدي رمز الشبكة الخاصية تلقائيا وتظهر خصائص هذا enum
مثل قائمة منسدلة.ولكن يمكن إنشاء أو تعديل التعداد في وقت التشغيل بحيث يمكن للمستخدم إضافة عقار آخر خيار و العودة إلى PropertyGrid
ورؤية خيار جديد في القائمة ؟
التحديث
معتبرا باتريك التعليق, أنا أفكر Enum
الصورة ليست الطريقة المناسبة في هذه الحالة.لذا بدلا من ذلك كيف يمكنني استخدام قائمة سلاسل لنشر المنسدلة في PropertyGrid
البند ؟ من شأنها أن تتطلب مخصص محرر ؟
المحلول
الجواب في فئة بسيطة: Typeconverter.. وبعد (ونعم، تعال ليست مناسبة هنا).
نظرا لأنني ليس لدي الكثير من التفاصيل، فسوف أفترض أن لديك خصائص "مرتبط" بمثيل استهداف من قبل خاصية SearseObject وأن مثيلك المستهدف ينفذ Isustomypedescriptor بحيث يمكنك إضافة خصائص (أي PropertyDescriptors) في وقت التشغيل. لا أعرف التصميم الخاص بك ولكن إذا كنت لا تفعل ذلك مثل هذا، فقد أنصحك بإلقاء نظرة عليه.
الآن دعنا نقول أنك تقوم بإضافة خاصية سلسلة وأنك تريد السماح لمستخدمك بتحديد مجموعة من القيود هذه الخاصية. UI الخاص بك دعنا المستخدم يدخل مجموعة من السلاسل وحصلت على قائمة بالسلاسل نتيجة لذلك. ربما يمكنك الاحتفاظ في قاموس الخصائص في مثيلك المستهدف، لذلك دعونا نفترض أن هذه القائمة الجديدة يتم تخزينها هناك أيضا.
الآن، ما عليك سوى كتابة محول جديد مشتق من Typeconverter (أو StringConverter ربما في هذا المثال). يجب عليك تجاوز GetStandardValuessUpported للعودة إلى TRUE و getstandardardvalues. لإرجاع قائمة السلاسل (استخدم معلمة السياق للوصول إلى خاصية المثيل وقائمة سلاسلها). سيتم نشر هذا المحول عن طريق الخصائص الخاصة بك مع خاصية PropertyDescriptor.converter.
آمل أن يكون هذا غير غامض للغاية. إذا كان لديك سؤال محدد في هذه العملية، فقط اسمحوا لي أن أعرف.
نصائح أخرى
الحل الهندسي النموذجي لمشكلتك هو استخدامها للحفاظ على القائمة كبيانات مرجعية في قاعدة البيانات الخاصة بك. بشكل عام، تهدف العادة العامة إلى أن تكون ثوابت محددة في وقت الترجمة، ويعد تعديلها في إصدار التعليمات البرمجية لاحقا (ناهيك عن وقت التشغيل)، حيث يمكن أن يسبب آثار جانبية في تبديل البيانات.
يمكنك إنشاء رمز باستخدام التعليمات البرمجية الخاصة بك، ثم احفظه على ملف نصي مؤقت، ثم استخدمه. سيكون هذا بطيئا لأنه ينطوي على استخدام 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
حلقات. milliseconds المنقضي:
11468.75 ل 1000000 التكرارات
مجموع (2000) = 2001000
القوة الغاشمة. milliseconds المنقضي:
406.25 مقابل 1000000 تكرارات
هنا هو رابط للفصل بأكمله إذا كنت ترغب في مزيد من المعلومات.
يمكنك استخدام التعداد.GetNames() و التعداد.GetValues() لإستعادة القيم و حيوي إضافة جديدة لهم.على الرغم من أنني أقترح عليك استخدام قائمة بدلا من التعداد أو التفكير في التصميم الخاص بك.شيء لا رائحة الحق.