我希望使用我的自定义控件的人通过简单的下拉列表选择常量。控件应该是可重用的,因此它驻留在库中,因此它不能访问枚举,并且实际上可以在任何地方定义常量。
问题是AppDomain.CurrentDomain.GetAssemblies()不包含您正在设计的当前项目。当我创建一个单独的项目时,它有时可以工作,但大多数情况下它并没有。并且我没有工作,我的意思是:它无法找到库中定义的任何类/常量,因此它们不会显示在下拉列表中。
其他一切都已经奏效了。下拉列表有效。代码生成是一个但很笨重,但它的工作原理。我总能找到TestControl本身定义的常量。
似乎设计师本身并不总是加载所有可用的组件/项目。那么如何在设计师/视觉工作室本身的环境中加载我需要的项目呢?或者如何在UITypeEditor中引用/查找我的依赖项列表中的其他项目?
我创建了自己的TestControl:
public partial class TestControl : UserControl, IHasTags
{
public static readonly ITestItem A = new TestItemImpl(null, "A", "Aap");
public static readonly ITestItem B = new TestItemImpl(null, "B", "B");
public static readonly ITestItem C_xx = new TestItemImpl(null, "C", "C");
public static readonly ITestItem D = new TestItemImpl(null, "D", "D");
[Editor(typeof(ItemEditor), typeof(UITypeEditor))]
public ITestItem item { get; set; }
public TestControl()
{
InitializeComponent();
}
}
用我自己的编辑:
public class ItemEditor : UITypeEditor
{
private IWindowsFormsEditorService _editorService;
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
// drop down mode (we'll host a listbox in the drop down)
return UITypeEditorEditStyle.DropDown;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
_editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
// use a list box
ListBox lb = new ListBox();
lb.SelectionMode = SelectionMode.One;
lb.SelectedValueChanged += OnListBoxSelectedValueChanged;
lb.Width = 300;
// get the analytic object from context
// this is how we get the list of possible benchmarks
TestControl ctrl = (TestControl)context.Instance;
// Add all test items
IList<StaticFieldInfo<ITestItem>> items = ClassHelper.getStaticFields<IHasTags, ITestItem>();
foreach (StaticFieldInfo<ITestItem> item in items)
{
lb.Items.Add(item);
}
// show this model stuff
_editorService.DropDownControl(lb);
if (lb.SelectedItem == null) // no selection, return the passed-in value as is
return value;
StaticFieldInfo<ITestItem> result = (StaticFieldInfo<ITestItem>)lb.SelectedItem;
return result.value;
}
private void OnListBoxSelectedValueChanged(object sender, EventArgs e)
{
// close the drop down as soon as something is clicked
_editorService.CloseDropDown();
}
}
我使用以下帮助器收集所有常量:
public class StaticFieldInfo<T>
{
public StaticFieldInfo(FieldInfo fieldInfo, T value)
{
this.fieldInfo = fieldInfo;
this.value = value;
}
public T value { get; private set; }
public FieldInfo fieldInfo { get; private set; }
public override string ToString()
{
return value.ToString() + " (" + fieldInfo.DeclaringType.FullName + "." + fieldInfo.Name + ")";
}
}
public class ClassHelper
{
public static IList<StaticFieldInfo<T>> getStaticFields<C, T>()
{
Type classType = typeof(C);
Type fieldType = typeof(T);
List<StaticFieldInfo<T>> result = new List<StaticFieldInfo<T>>();
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
foreach (Type t in asm.GetTypes())
{
if (classType.IsAssignableFrom(t))
{
foreach (FieldInfo f in t.GetFields())
{
if (f.IsStatic && fieldType.IsAssignableFrom(f.FieldType))
{
T value = (T)f.GetValue(null);
if (value != null)
{
result.Add(new StaticFieldInfo<T>(f, value));
}
}
}
}
}
}
catch (ReflectionTypeLoadException)
{
// Ignore errors
}
}
return result;
}
}
其余代码(不太重要):
public interface IHasTags
{
}
[TypeConverter(typeof(ITestItemConverter))]
public interface ITestItem
{
string driver { get; set; }
string tag { get; set; }
string name { get; set; }
}
public class ITestItemConverter : TypeConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
// we only know how to convert from to a string
return typeof(string) == destinationType;
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value != null)
{
return value.ToString();
}
return "(none)";
}
}
[DesignerSerializer(typeof(ItemSerializer), typeof(CodeDomSerializer))]
public class TestItemImpl : ITestItem
{
public TestItemImpl()
{
}
public TestItemImpl(string driver, string tag, string name)
{
this.driver = driver;
this.tag = tag;
this.name = name;
}
public override string ToString()
{
return name;
}
public string driver { get; set; }
public string tag { get; set; }
public string name { get; set; }
}
public class ItemSerializer : CodeDomSerializer
{
public override object Deserialize(IDesignerSerializationManager manager, object codeObject)
{
// This is how we associate the component with the serializer.
CodeDomSerializer baseClassSerializer = (CodeDomSerializer)manager.
GetSerializer(typeof(TestItemImpl).BaseType, typeof(CodeDomSerializer));
/* This is the simplest case, in which the class just calls the base class
to do the work. */
return baseClassSerializer.Deserialize(manager, codeObject);
}
public override object Serialize(IDesignerSerializationManager manager, object value)
{
/* Associate the component with the serializer in the same manner as with
Deserialize */
CodeDomSerializer baseClassSerializer = (CodeDomSerializer)manager.
GetSerializer(typeof(TestItemImpl).BaseType, typeof(CodeDomSerializer));
object codeObject = baseClassSerializer.Serialize(manager, value);
/* Anything could be in the codeObject. This sample operates on a
CodeStatementCollection. */
if (codeObject is CodeStatementCollection)
{
CodeStatementCollection statements = (CodeStatementCollection)codeObject;
StaticFieldInfo<ITestItem> field = null;
foreach (StaticFieldInfo<ITestItem> item in ClassHelper.getStaticFields<IHasTags, ITestItem>())
{
if (value == item.value)
{
field = item;
break;
}
}
// If field can be found (should be always)
if (field != null)
{
statements = new CodeStatementCollection();
statements.Add(new CodeSnippetExpression(typeof(ITestItem).FullName + " " + this.GetUniqueName(manager, value) + " = " + typeof(TestControl).FullName + "." + field.fieldInfo.Name));
codeObject = statements;
}
}
return codeObject;
}
}