3

プロパティとして aを使用して WinForms ユーザーコントロールを作成しようとしてCollection<T>います (ここで、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 に設定します。Content に設定されている場合、デザイナーは何も割り当てません。
  • 私もこれを見つけました:デザイン時に編集できるコレクションを使用して UserControl を作成する方法は? しかし、 CollectionBase クラスはすでにこれを行っています。
  • いろいろとデバッグしていますが、例外がないので何が悪いのかさっぱりわかりません。コレクション フォームの終了イベントにイベント リスナーを追加すると、コレクション エディターでいくつかのステップを追加しても、(コレクション フォームの) EditValue プロパティが null のままであることがわかりました。しかし、それがなぜなのかは私にもわかりません...

この投稿を終えたときに、次のトピックを見つけました: DesignMode でコレクションを編集する最も簡単な方法は? 私が経験したのとまったく同じ問題ですが、標準コレクションを使用していないため、提案された回答を使用できません。

4

2 に答える 2

1

CodeProject のこの Greate 記事を確認してください。両方をテストしましたが、動作します。

あなたが適用しなかった主な重要な違いだと思います:

  • コレクションの PropertyChanged をサポート
  • InstanceDescriptor をサポートするコレクション アイテム クラスの TypeConverter を作成します。
于 2015-08-28T09:08:09.760 に答える
0

Reza Aghaei が言及した記事は非常に興味深いものです。しかし、私は私の問題に対するより簡単な解決策に近づいていると思います:

既に気づいたように、コレクションにアイテムを追加したにもかかわらず、collectionForm の EditValue プロパティは null のままでした。さて、コレクション エディターの 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;
}

メソッド内の 2 行目に注目してください。これは、新しい 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 から継承させる必要があるかもしれません (コンポーネントは常にデザイナー ファイルで初期化されるため)。

これは私の最初の質問とはまったく異なる質問であることを知っているので、このために新しい質問を開始するかもしれません. しかし、誰かがすでに答えを知っているなら、ここでそれを聞くのは素晴らしいことです!

更新: 問題の解決策を見つけました。

C# では許可されていないため、Component および CollectionBase からの継承はできませんでした。カスタム コレクションを InstanceDescriptor に変換する TypeConverter の実装も機能しませんでした (理由はわかりません。コレクションが通常のカスタム クラスとは異なる方法でシリアル化されているためだと思います)。

しかし、 を作成することで、作成した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 に答える