我编写了自己的UITypeEditor,并在@Sefe和THIS链接的帮助下实现了我的目标。
在此设置中,BaseForm
扩展了System.Windows.Forms.Form
。在这里,我将我的属性(List<Control> ActionButtons
)放置为具有自定义UITypeEditor的模式样式。
恢复工作流程(所有这些都在设计时间内):
1 - 打开MyForm
2 - 在MyForm属性面板上单击ActionButtons(由BaseForm继承)省略号[...]
3 - 打开自定义表单,其中包含我想要选择的膨胀对象。 (此表单由我的CustomUITypeEditor调用)
4 - 拾取我想要的对象,然后关闭表单。现在,数据在MyForm
中正常,并序列化为Designer.cs文件。我可以重新打开EditorUI再次点击省略号并查看我之前选择的对象
5 - 现在当我关闭MyForm并重新打开它时,所有数据都会丢失!但数据仍然序列化为Designer.cs。
6 - 如果我使用string
代替List<Control>
完成所有这些步骤,则一切都正常
我的代码:
public class CollectionTypeEditor : UITypeEditor {
private IWindowsFormsEditorService _editorService = null;
private ICollection<Control> mControls = null;
private List<Control> mPickedControls = null;
// Editor like Modal style
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) {
return UITypeEditorEditStyle.Modal;
}
// Opens modal and get returned data
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
if (provider == null)
return value;
_editorService = (IWindowsFormsEditorService) provider
.GetService(typeof(IWindowsFormsEditorService));
if (_editorService == null)
return value;
mControls = new List<Control>();
// retrieve old data
mPickedControls = value as List<Control>;
if (mPickedControls == null)
mPickedControls = new List<Control>();
// getting existent controls that will be inflated in modal
Control mContext = (Control) context.Instance;
GetControls(mContext);
// open form and get response
CollectionDesign<Control> frmCollections = new CollectionDesign<Control>(mControls, ref mPickedControls);
var response = _editorService.ShowDialog(frmCollections);
// returning data from editor
return response == DialogResult.OK ? mPickedControls : value;
}
这里的一切都运作良好。 BaseForm
中我变量的代码。 Ps:这个变量将显示在MyForm
上,我点击省略号[...]
[Editor(typeof(CollectionTypeEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ActionButtonConverter))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<Control> ActionButtons { get; set; }
添加了序列化属性,因为无法保存文件。当表单关闭并重新打开时,所有数据都将丢失。
奇怪的是,我写了其他UITypeEditor,就像一样,只是将数据类型更改为string
,我可以关闭或重新打开我的表单,一切正常,数据得救了。
我已经添加了一个TypeConverter,但我认为这不是一个案例。我的代码出了什么问题?
我在重新打开的表单上收到此错误:
Severity Code Description Project File Line Suppression State Message Method 'System.CodeDom.CodePropertyReferenceExpression.Add' not found.
现在我的控件列表在关闭或重新打开时存储在myForm.designer文件中,但控件没有附加在属性网格上。即:如果我在关闭时单击省略号添加按钮'addbt'并重新打开myForm,则会自动生成代码。
在myForm.designer上自动生成代码:
//
// addbt
//
this.addbt.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.addbt.Cursor = System.Windows.Forms.Cursors.Hand;
this.addbt.Font = new System.Drawing.Font("Tahoma", 9F);
this.addbt.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(222)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
this.addbt.Location = new System.Drawing.Point(13, 6);
this.addbt.Name = "addbt";
this.addbt.Size = new System.Drawing.Size(103, 33);
this.addbt.TabIndex = 0;
this.addbt.Text = "Incluir";
this.addbt.UseVisualStyleBackColor = true;
//
// myForm
//
this.ActionButtons.Add(this.addbt);
如果我再次点击省略号,数据将附加到myForm的PropertyGrid上的属性。但是当我重新打开myForm时,之前存储的值不会传递给myForm的PropertyGrid上的属性(数据仍存储在自动生成的代码设计器中)。因此,当点击省略号[...]时,value
方法中的EditValue
不会存储数据。我觉得它更接近:)
下面我的TypeConverter可能出现问题:
public class ActionButtonConverter : TypeConverter {
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) {
if (sourceType == (typeof(string)))
return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) {
if(value.GetType() == typeof(string)) {
List<Control> ctrs = value as List<Control>;
if (ctrs != null)
return ctrs;
}
return base.ConvertFrom(context, culture, value);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) {
if (destinationType == typeof(string))
return true;
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) {
if (value == null)
return "null";
if (destinationType == typeof(string))
return "(custom collection)";
return base.ConvertTo(context, culture, value, destinationType);
}
我认为反序列化中有任何错误。重新打开myForm时,ITypeDescriptorContext或值(TypeConverter)没有任何关于序列化数据到designer.cs文件的内容。
谢谢大家,对不好的语言表示抱歉:P
答案 0 :(得分:1)
WinForms设计时支持是一个棘手的主题。为了更好地理解:所发生的是正在设计的表单变成代码。此过程称为设计时序列化。序列化由CodeDomSerializer
执行,可以使用DesignerSerializerAttribute
应用于属性。您可以在表单的自动生成文件中查看生成的代码。当您执行WinForms设计时间时,您有时必须检查序列化程序输出(在 FormName.Designer.cs 文件中)。我相信在你的情况下,没有输出会将控件添加到容器控件中(否则关闭并重新打开表单不会有问题)。
默认的序列化程序可以处理大多数序列化情况,主要是在TypeConverter
的帮助下。所以通常不必创建自己的序列化程序,但有时你必须这样做。你的情况就是这样。
您添加到帖子中的错误消息是CodeDom错误消息,因此肯定必须来自序列化程序,因为这样就无法创建源代码。我认为您在这种特殊情况下的问题是您不会在列表中添加新项目,而是在表单上已存在的其他控件。通常,默认序列化程序将为集合中的每个元素创建一个新项。但是,这不是您需要在此处执行的操作,因为您要将现有项添加到集合中(这就是它与string
一起使用的原因,因为它始终可以创建为文字)。
您的解决方案是创建自己的CodeDomSerializer
,通过设计时架构查找添加的控件(您可能需要IReferenceService
)并添加CodeDom图以添加现有的项目到您的收藏。
例如,默认序列化程序创建的代码如下所示:
this.myControl.ActionButtons.Add(new Button());
您的代码必须如下所示:
this.myControl.ActionButtons.Add(this.myActionButton);
这意味着首先使用IReferenceService
获取按钮的名称(您只有集合中的对象),然后创建一个CodeDom图表,将此按钮添加到您的属性中。为此,您必须覆盖CodeDomSerializer.SerializeProperty
并拦截ActionButtons
属性的序列化(确保为所有其他属性调用基类),您可以在其中进行序列化。
答案 1 :(得分:0)
几天后,我找到了解决这个问题的方法。我解决了它创建我自己的Button集合,它继承了CollectionBase:
public class ButtonCollection : CollectionBase {
public CustomButton this[int i] {
get { return InnerList[i] as CustomButton; }
set { InnerList[i] = value; }
}
public ButtonCollection() {
}
public CustomButton Add(CustomButton bt) {
InnerList.Add(bt);
return bt;
}
public void AddRange(CustomButton[] bts) {
InnerList.AddRange(bts);
}
public void Remove(CustomButton bt) {
InnerList.Remove(bt);
}
public bool Contains(CustomButton bt) {
return InnerList.Contains(bt);
}
public CustomButton[] GetValues() {
CustomButton[] bts = new CustomButton[InnerList.Count];
InnerList.CopyTo(bts);
return bts;
}
我还对TypeConverter进行了更改:
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo info, object value, Type destType) {
if ((destType == typeof(string)) && (value is CustomButton)) {
CustomButton bt = (CustomButton) value;
return bt.Name;
}
// this helped me a lot
// here the object needs to know how to create itself
// Type[0] can be overridden by Type[] { (your constructor parameterTypes) }
// null can be overridden by objects that will be passed how parameter
// third parameter is a value indicating if the initialization of the object is or not complete
else if (destType == typeof(InstanceDescriptor)) {
return new InstanceDescriptor(
typeof(CustomButton).GetConstructor(new Type[0]),
null,
false
);
}
return base.ConvertTo(context, info, value, destType);
}
TypeConverter Decorator传递给CustomButton
类:
[TypeConverter(typeof(CustomButtonConverter))]
public class CustomButton {
...
我在完成自定义collectionsEditor示例之后完成了所有这些,可以找到here。