3

Collection<T>我正在尝试制作一个带有 a作为属性的 WinForms 用户控件(其中 T 代表一些自定义类)。我已经阅读了很多关于这个主题的内容,但是我不能让它在设计时正常工作(在运行时一切正常)。更准确地说:当我单击属性窗口中的“...”按钮时,集合编辑器显示正常,我可以添加和删除项目。但是当我单击 OK 按钮时,什么也没有发生,当我重新打开集合编辑器时,所有项目都丢失了。当我查看设计器文件时,我看到我的属性被分配为 null,而不是组合集合。我将向您展示最重要的代码:

用户控制:

[Browsable(true),
 Description("The different steps displayed in the control."),
 DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
 Editor(typeof(CustomCollectionEditor), typeof(UITypeEditor))]
public StepCollection Steps
{
    get
    {
        return wizardSteps;
    }
    set
    {
        wizardSteps = value;
        UpdateView(true);
    }
}

StepCollection 类:

public class StepCollection : System.Collections.CollectionBase
{
    public StepCollection() : base() { }
    public void Add(Step item) { List.Add(item); }
    public void Remove(int index) { List.RemoveAt(index); }
    public Step this[int index]
    {
        get { return (Step)List[index]; }
    }
}

阶梯类:

[ToolboxItem(false),
DesignTimeVisible(false),
Serializable()]
public class Step : Component
{
    public Step(string name) : this(name, null, StepLayout.DEFAULT_LAYOUT){ }
    public Step(string name, Collection<Step> subSteps) : this(name, subSteps, StepLayout.DEFAULT_LAYOUT){ }
    public Step(string name, Collection<Step> subSteps, StepLayout stepLayout)
    {
        this.Name = name;
        this.SubSteps = subSteps;
        this.Layout = stepLayout;
    }
    // In order to provide design-time support, a default constructor without parameters is required:
    public static int NEW_ITEM_ID = 1;
    public Step()
        : this("Step" + NEW_ITEM_ID, null, StepLayout.DEFAULT_LAYOUT)
    {
        NEW_ITEM_ID++;
    }
    // Some more properties
}

自定义集合编辑器:

class CustomCollectionEditor : CollectionEditor
{
    private ITypeDescriptorContext mContext;

    public CustomCollectionEditor(Type type) : base(type) { }

    public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
    {
        mContext = context;
        return base.EditValue(context, provider, value);
    }
    protected override object CreateInstance(Type itemType)
    {
        if (itemType == typeof(Step))
        {
            Step s = (Step)base.CreateInstance(itemType);
            s.parentContext = mContext; // Each step needs a reference to its parentContext at design time
            return s;
        }
        return base.CreateInstance(itemType);
    }
}

我已经尝试过的事情:

  • 使 Step 类成为此处所述的组件:http: //www.codeproject.com/Articles/5372/How-to-Edit-and-Persist-Collections-with-Collectio
  • 更改Collection<Step>为继承 System.Collections.CollectionBase 的自定义集合类StepCollection(在之前的代码项目文章中也有描述)
  • 将 DesignerSerializationVisibility 设置为 Content,如下所述:设计时用户控件中的集合编辑器当它设置为 Visible 时,设计器将 null 分配给我的属性;当它设置为内容时,设计者什么也不分配。
  • 我还发现了这个:如何使用可以在设计时编辑的集合制作用户控件?但是 CollectionBase 类已经为我做了这个。
  • 调试了很多,但由于没有例外,我真的不知道出了什么问题。当我向 collectionForm 的关闭事件添加事件侦听器时,即使我在集合编辑器中添加了几个步骤,我也可以看到(collectionForm 的)EditValue 属性仍然为空。但我也不知道这是为什么...

完成这篇文章时,我刚刚发现了这个主题:在设计模式中编辑集合的最简单方法? 这与我遇到的问题完全相同,但是我不能使用建议的答案,因为我不使用标准集合。

4

2 回答 2

1

查看 CodeProject 上的这篇 Greate 文章,我对它们都进行了测试并且它们都有效。

我认为您未应用的主要关键区别:

  • 支持您的收藏的 PropertyChanged
  • 为支持 InstanceDescriptor 的 Collection Item Class 创建一个 TypeConverter。
于 2015-08-28T09:08:09.760 回答
0

Reza Aghaei 提到的文章非常有趣。但是我认为我已经接近解决我的问题的更简单的方法:

正如我已经注意到的,collectionForm 的 EditValue 属性保持为空,尽管向集合中添加了项目。现在,我实际上并不确定集合编辑器的 EditValue 方法内部发生了什么,但我猜它捕获了一个异常,因为我的集合的初始值为 null(它未在构造函数中初始化),因此返回 null 而不是创建一个新的集合。通过在我的自定义集合编辑器类中进行以下更改,我得到了非常有希望的结果:

public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
    mContext = context;
    if (value == null) value = new Collection<Step>();
    Collection<Step> result = (Collection<Step>)base.EditValue(context, provider, value);
    if (result != null && result.Count == 0) return null;
    return result;
}

请注意方法中的第二行,它将一个新的 Collection 分配给初始值。通过这样做,我的收藏得以保留,并且一切正常。

我现在唯一要修复的是设计器文件的序列化。目前产生了这样的东西:

// wizardStepsControl1
// ...
this.wizardStepsControl1.Steps.Add(this.step1);
// ...

// step1
// Initialization of step1

此代码将给出异常,因为 wizardStepsControl1.Steps 从未初始化为集合。我想要制作的是这样的:

this.wizardStepsControl1.Steps = new Collection<Step>();
this.wizardStepsControl1.Steps.Add(step1);
// ...

更好的是,首先初始化整个集合,然后将其分配给我的控件的 Steps 属性。我会看看我能做些什么让它工作并在这里发布一些更新,也许它需要实现 InstanceDescriptor 或让我的自定义 Collection 类继承自 Component(因为组件总是在设计器文件中初始化)。

我知道这是一个与我的第一个问题完全不同的问题,所以也许我会为此开始一个新问题。但是,如果有人已经知道答案,那么在这里听到它会很棒!

更新: 我找到了解决问题的方法。

从 Component 和 CollectionBase 继承是不可能的,因为 C# 不允许这样做。实现将我的自定义集合转换为 InstanceDescriptor 的 TypeConverter 也不起作用(我不知道为什么,我猜这是因为 Collection 以不同于普通自定义类的方式序列化)。

但是通过创建CodeDomSerializer,我能够将代码添加到生成的设计师的代码中。这样,如果在设计期间添加了一些项目,我就可以初始化我的集合:

public class WizardStepsSerializer : CodeDomSerializer
{
    /// <summary>
    /// We customize the output from the default serializer here, adding
    /// a comment and an extra line of code.
    /// </summary>
    public override object Serialize(IDesignerSerializationManager manager, object value)
    {
        // first, locate and invoke the default serializer for 
        // the ButtonArray's  base class (UserControl)
        //
        CodeDomSerializer baseSerializer = (CodeDomSerializer)manager.GetSerializer(typeof(WizardStepsControl).BaseType, typeof(CodeDomSerializer));

        object codeObject = baseSerializer.Serialize(manager, value);

        // now add some custom code
        //
        if (codeObject is CodeStatementCollection)
        {

            // add a custom comment to the code.
            //
            CodeStatementCollection statements = (CodeStatementCollection)codeObject;
            statements.Insert(4, new CodeCommentStatement("This is a custom comment added by a custom serializer on " + DateTime.Now.ToLongDateString()));

            // call a custom method.
            //
            CodeExpression targetObject = base.SerializeToExpression(manager, value);
            WizardStepsControl wsc = (WizardStepsControl)value;
            if (targetObject != null && wsc.Steps != null)
            {
                CodePropertyReferenceExpression leftNode = new CodePropertyReferenceExpression(targetObject, "Steps");
                CodeObjectCreateExpression rightNode = new CodeObjectCreateExpression(typeof(Collection<Step>));
                CodeAssignStatement initializeStepsStatement = new CodeAssignStatement(leftNode, rightNode);
                statements.Insert(5, initializeStepsStatement);
            }

        }

        // finally, return the statements that have been created
        return codeObject;
    }
}

通过使用 将此序列化程序与我的自定义控件相关联DesignerSerializerAttribute,在设计器文件中生成以下代码:

// 
// wizardStepsControl1
// 
// This is a custom comment added by a custom serializer on vrijdag 4 september 2015
this.wizardStepsControl1.Steps = new System.Collections.ObjectModel.Collection<WizardUserControl.Step>();
// ...
this.wizardStepsControl1.Steps.Add(step1);
// ...

这正是我想要的。

我从https://msdn.microsoft.com/en-us/library/system.componentmodel.design.serialization.codedomserializer(v=vs.110).aspx获取了大部分代码

于 2015-08-29T10:31:51.117 回答