How to save collection data in Design Time with Custom UITypeEditor C# WindowsFormApplication?

随声附和 提交于 2019-12-11 05:46:21

问题


I wrote my own UITypeEditor and I reached my goal with help of @Sefe and with THIS link.

My basic setup:

In this setup, BaseForm extends System.Windows.Forms.Form. Here i'm putting my property (List<Control> ActionButtons) that have a custom UITypeEditor like a modal style.

Resumed workflow (All here are in Design Time):

    1 - Open MyForm.
    2 - Click at ActionButtons(inherited by BaseForm) ellipsis [...] on MyForm properties panel.
    3 - A custom form is opened with inflated objects that I want pick. (this form is called by my CustomUITypeEditor)
    4 - Objects that I want are picked, and I close the form. So now, the data are ok in MyForm and serialized into Designer.cs file. I can reopen that EditorUI clicking again in ellipsis and see objects that I picked before.
    5 - Now when I close MyForm and reopen it, all data are lost!!! But the data still serialized into Designer.cs.     6 - If I do all this steps with a string instead List<Control>, all are Ok.

Bellow my code:

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;
}

Everything works well here. Code for my variable in BaseForm. Ps.: this variable will be showed on MyForm where I make click at ellipsis [...]

[Editor(typeof(CollectionTypeEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ActionButtonConverter))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<Control> ActionButtons { get; set; }

The serialization attribute was added because the file couldn't be saved. When form is closed and reopened, all data are lost.

The stranger thing is that I wrote other UITypeEditor like the same way, just changing type of data to string and I can close or reopen my form and all works fine, the data are saved.

I already added a TypeConverter but I think that isn't case here. what is wrong with my code?

I'm getting this error on reopen form:

Severity Code Description Project File Line Suppression State Message Method 'System.CodeDom.CodePropertyReferenceExpression.Add' not found.        

UPDATE

Now my List of controls are stored in myForm.designer file when it's closed or reopened, but the controls don't are attached on property grid. i.e.: If I click on ellipsis to add a Button 'addbt' when close and reopen myForm the code is auto generated.

Code auto generated bellow on 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);

If I make a click on ellipsis again, the data are attached to property on myForm's PropertyGrid. But when I reopen myForm, this values stored before aren't passed to property on myForm's PropertyGrid (data still stored in auto generated code designer). So when a click on the ellipsis [...] the value from EditValue method not comes with data stored. I'm feeling that it's closer :)

Maybe something is wrong with my TypeConverter below:

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);
}

I think that I have anything error in deserialization. When myForm is reopened the ITypeDescriptorContext or value (TypeConverter) don't have anything about serialized data into designer.cs file.

Thanks all, and sorry for bad language :P


回答1:


WinForms design time support is a tricky subject. For better understanding: What happens is that the form that is being designed is turned into code. This process is called design time serialization. The serialization is performed by a CodeDomSerializer, which can be applied to the property with a DesignerSerializerAttribute. You can see your generated code in the auto-generated file for your form. When you do WinForms design time debelopment, you will sometimes have to check the serializer output (in the FormName.Designer.cs file). I'm sure in your case there will be no output that will add the controls to your container control (otherwise closing and reopening the form would not be a problem).

The default serializer can handle most of the serialization cases, mostly with the help of a TypeConverter. So you usually won't have to create your own serializer, but sometimes you have to do it. Your case is such a case.

The error message you have added to your post is a CodeDom error message, so it surely must come from the serializer, since this is wehere the source code is created. I think your issue in this particular case is that you don't add new items to your list, but other controls that already exist on your form. Normally, the default serializer will create a new item for each element in the collection. However, this is not what you need to do here, because you want to add existing items to the collection (that's why it works with a string, since it can be always created a literal).

Your solution is to create your own CodeDomSerializer that looks for the added controls through the design-time architecture (you will probably need an IReferenceService for that) and adds the CodeDom graph to add the existing item to your collection.

For example, where the default serializer creates code looking like this:

this.myControl.ActionButtons.Add(new Button());

Your code would have to look like this:

this.myControl.ActionButtons.Add(this.myActionButton);

That means first getting the name of your button (you only have the object in the collection) with the IReferenceService and then create a CodeDom graph that adds this button to your property. For that you will have to overwrite CodeDomSerializer.SerializeProperty and intercept the serialization of your ActionButtons property (be sure to call the base class for all other properties), where you can do your serialization.




回答2:


After some days I found the solution for this problem. I solved it creating my own Button collection that inherits of 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;
}

I also made changes to the 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 passed to CustomButton class:

[TypeConverter(typeof(CustomButtonConverter))]
public class CustomButton { 
...

I finalized all this following a fully example of custom collectionsEditor that can be found here.



来源:https://stackoverflow.com/questions/40921778/how-to-save-collection-data-in-design-time-with-custom-uitypeeditor-c-sharp-wind

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!