web-dev-qa-db-ja.com

Visual Studio 2008 Windowsフォームデザイナを取得して、抽象基本クラスを実装するフォームをレンダリングするにはどうすればよいですか?

Windows Formsの継承されたコントロールに問題が生じたため、いくつかのアドバイスが必要です。

リスト(パネルで構成された自作のGUIリスト)の項目と、リストに追加できるデータの各タイプ用の継承されたコントロールの基本クラスを使用します。

それには問題はありませんでしたが、ベースコントロールを抽象クラスにすることが正しいことがわかりました。これは、メソッドを持ち、継承されたすべてのコントロールに実装する必要があり、基本制御ですが、基本クラスに実装してはならず、実装することはできません。

基本コントロールを抽象としてマークすると、Visual Studio 2008 Designerはウィンドウのロードを拒否します。

抽象化された基本コントロールでDesignerを動作させる方法はありますか?

97

私はこれを行う方法がなければならないと知っていました(そして、私はこれをきれいに行う方法を見つけました)。 Shengのソリューションはまさに一時的な回避策として思いついたものですが、友人がFormクラスが最終的にabstractクラスから継承したことを指摘した後、私たちはこれを成し遂げるべきです。彼らができるなら、私たちはそれをすることができます。

このコードから問題に進みました

Form1 : Form

問題

public class Form1 : BaseForm
...
public abstract class BaseForm : Form

これが最初の質問が出てきた場所です。前に述べたように、友人はSystem.Windows.Forms.Formが抽象である基本クラスを実装することを指摘しました。見つけることができました...

より良いソリューションの証明

このことから、デザイナーが基本抽象クラスを実装したクラスを表示できることはわかっていましたが、基本抽象クラスをすぐに実装したデザイナークラスは表示できませんでした。最大で5つの中間にある必要がありましたが、抽象化の1つのレイヤーをテストし、最初にこのソリューションを考え出しました。

初期ソリューション

public class Form1 : MiddleClass
...
public class MiddleClass : BaseForm
... 
public abstract class BaseForm : Form
... 

これは実際に機能し、デザイナーは問題を解決して問題なくレンダリングします。ただし、winformsデザイナーが不十分であるために必要だったプロダクションアプリケーションの継承レベルが余分にある場合を除きます。

これは100%確実な解決策ではありませんが、かなり良いです。基本的に、#if DEBUGを使用して洗練されたソリューションを考え出します。

洗練されたソリューション

Form1.cs

#if DEBUG
public class Form1 : MiddleClass
#else 
public class Form1 : BaseForm
#endif
...

MiddleClass.cs

public class MiddleClass : BaseForm
... 

BaseForm.cs

public abstract class BaseForm : Form
... 

これが行うことは、デバッグモードの場合にのみ、「初期ソリューション」で説明したソリューションを使用することです。アイデアは、デバッグビルドを介してプロダクションモードを決してリリースせず、常にデバッグモードで設計するということです。

デザイナーは常に現在のモードでビルドされたコードに対して実行されるため、リリースモードでデザイナーを使用することはできません。ただし、デバッグモードで設計し、リリースモードでビルドされたコードをリリースする限り、問題ありません。

唯一の確実な解決策は、プリプロセッサディレクティブを介してデザインモードをテストできる場合です。

95
smelch

@smelch、より良い解決策があります。デバッグ用であっても、中間コントロールを作成する必要はありません。

必要なもの

最初に、最終クラスと基本抽象クラスを定義しましょう。

public class MyControl : AbstractControl
...
public abstract class AbstractControl : UserControl // Also works for Form
...

ここで必要なのは説明プロバイダーだけです。

public class AbstractControlDescriptionProvider<TAbstract, TBase> : TypeDescriptionProvider
{
    public AbstractControlDescriptionProvider()
        : base(TypeDescriptor.GetProvider(typeof(TAbstract)))
    {
    }

    public override Type GetReflectionType(Type objectType, object instance)
    {
        if (objectType == typeof(TAbstract))
            return typeof(TBase);

        return base.GetReflectionType(objectType, instance);
    }

    public override object CreateInstance(IServiceProvider provider, Type objectType, Type[] argTypes, object[] args)
    {
        if (objectType == typeof(TAbstract))
            objectType = typeof(TBase);

        return base.CreateInstance(provider, objectType, argTypes, args);
    }
}

最後に、TypeDescriptionProvider属性をAbastractコントロールに適用します。

[TypeDescriptionProvider(typeof(AbstractControlDescriptionProvider<AbstractControl, UserControl>))]
public abstract class AbstractControl : UserControl
...

以上です。中間管理は不要です。

また、プロバイダークラスは、同じソリューションで必要な数のAbstractベースに適用できます。

* EDIT *また、app.configには以下が必要です。

<appSettings>
    <add key="EnableOptimizedDesignerReloading" value="false" />
</appSettings>

提案をありがとう@ user3057544.


71
jucardi

@Smelch、役立つ答えをありがとう。最近同じ問題にぶつかりました。

以下は、コンパイルの警告を防ぐための投稿への小さな変更です(#if DEBUGプリプロセッサディレクティブ内に基本クラスを配置することにより):

public class Form1
#if DEBUG  
 : MiddleClass 
#else  
 : BaseForm 
#endif 
10
Dave Clemmer

私は同様の問題を抱えていましたが、抽象基本クラスの代わりにインターフェースを使用するようにリファクタリングする方法を見つけました:

interface Base {....}

public class MyUserControl<T> : UserControl, Base
     where T : /constraint/
{ ... }

これはすべての状況に適用できるわけではありませんが、可能であれば、条件付きコンパイルよりもクリーンなソリューションになります。

5
Jan Hettich

私はJuan Carlos Diazのソリューションのヒントを持っています。それは私にとってはうまく機能しますが、それにはいくつかの問題がありました。 VSを起動してデザイナーに入ると、すべて正常に動作します。ただし、ソリューションを実行した後、それを停止して終了し、デザイナーを入力しようとすると、VSを再起動するまで例外が何度も表示されます。しかし、私はそれのための解決策を見つけました-行うべきことはすべてapp.configに以下を追加することです

  <appSettings>
   <add key="EnableOptimizedDesignerReloading" value="false" />
  </appSettings>
3
user3057544

this answer のソリューションを別の質問に使用しています。これは this article にリンクしています。この記事では、抽象クラスのカスタムTypeDescriptionProviderおよび具象実装を使用することを推奨しています。デザイナーは、使用する型をカスタムプロバイダーに要求します。抽象クラスが具象クラスとしてどのように表示されるかを完全に制御しながら、デザイナーが満足できるように、コードが具象クラスを返すことができます。

更新:他の質問への回答に 文書化されたコードサンプル を含めました。そこのコードは動作しますが、動作するために私の回答に記載されているように、クリーン/ビルドサイクルを実行する必要がある場合があります。

3
Carl G

Juan Carlos DiazによるTypeDescriptionProviderが機能せず、条件付きコンパイルも好きではないと言う人々のために、いくつかのヒントを持っています:

まず、フォームデザイナでコードの変更が機能するためにVisual Studioを再起動する必要があります(単純な再構築が機能しなかった-または毎回機能しなかった)。

抽象ベースフォームの場合のこの問題の解決策を紹介します。 BaseFormクラスがあり、それに基づいたフォームを設計可能にしたいとします(これは_Form1_になります)。 Juan Carlos Diazが提示したTypeDescriptionProviderは私にとってもうまくいきませんでした。次に、MiddleClassソリューションに(smelchで)参加することにより、どのように動作するかを示しますが、_#if DEBUG_条件付きコンパイルといくつかの修正なし:

_[TypeDescriptionProvider(typeof(AbstractControlDescriptionProvider<BaseForm, BaseFormMiddle2>))]   // BaseFormMiddle2 explained below
public abstract class BaseForm : Form
{
    public BaseForm()
    {
        InitializeComponent();
    }

    public abstract void SomeAbstractMethod();
}


public class Form1 : BaseForm   // Form1 is the form to be designed. As you see it's clean and you do NOTHING special here (only the the normal abstract method(s) implementation!). The developer of such form(s) doesn't have to know anything about the abstract base form problem. He just writes his form as usual.
{
    public Form1()
    {
        InitializeComponent();
    }

    public override void SomeAbstractMethod()
    {
        // implementation of BaseForm's abstract method
    }
}
_

BaseFormクラスの属性に注目してください。次に、TypeDescriptionProvider2つの中間クラスを宣言する必要がありますが、心配しないでください。これらはForm1の開発者には見えず、無関係ですです。最初のものは、抽象メンバーを実装します(そして、基本クラスを非抽象にします)。 2番目は空です-VSフォームデザイナが機能するために必要なだけです。次に、second中間クラスをTypeDescriptionProviderBaseFormに割り当てます。 条件付きコンパイルなし

さらに2つの問題がありました:

  • 問題1:デザイナ(またはいくつかのコード)でForm1を変更した後、再びエラーが発生していました(デザイナで再び開こうとすると)。
  • 問題2:デザイナでForm1のサイズが変更され、フォームが閉じられ、フォームデザイナで再度開かれたときに、BaseFormのコントロールが誤って配置されました。

最初の問題(私のプロジェクトで他のいくつかの場所に出没し、通常「タイプXをタイプXに変換できない」例外を生成するものであるため、あなたはそれを持たないかもしれません)。タイプを比較するのではなく、タイプ名を比較する(FullName)によってTypeDescriptionProviderで解決しました(以下を参照)。

2番目の問題。基本フォームのコントロールがForm1クラスで設計可能でなく、サイズ変更後にその位置が失われる理由はわかりませんが、それを回避しました(ニースのソリューションではありません-よろしければ、書いてください)。 BaseFormのLoadイベントから非同期に呼び出されるメソッドで、BaseFormのボタン(右下隅にあるはずです)を手動で正しい位置に移動するだけです:BeginInvoke(new Action(CorrectLayout));私の基本クラスには「OK」と「キャンセル」ボタンなので、ケースは簡単です。

_class BaseFormMiddle1 : BaseForm
{
    protected BaseFormMiddle1()
    {
    }

    public override void SomeAbstractMethod()
    {
        throw new NotImplementedException();  // this method will never be called in design mode anyway
    }
}


class BaseFormMiddle2 : BaseFormMiddle1  // empty class, just to make the VS designer working
{
}
_

そして、ここにTypeDescriptionProviderのわずかに変更されたバージョンがあります:

_public class AbstractControlDescriptionProvider<TAbstract, TBase> : TypeDescriptionProvider
{
    public AbstractControlDescriptionProvider()
        : base(TypeDescriptor.GetProvider(typeof(TAbstract)))
    {
    }

    public override Type GetReflectionType(Type objectType, object instance)
    {
        if (objectType.FullName == typeof(TAbstract).FullName)  // corrected condition here (original condition was incorrectly giving false in my case sometimes)
            return typeof(TBase);

        return base.GetReflectionType(objectType, instance);
    }

    public override object CreateInstance(IServiceProvider provider, Type objectType, Type[] argTypes, object[] args)
    {
        if (objectType.FullName == typeof(TAbstract).FullName)  // corrected condition here (original condition was incorrectly giving false in my case sometimes)
            objectType = typeof(TBase);

        return base.CreateInstance(provider, objectType, argTypes, args);
    }
}
_

そしてそれだけです!

BaseFormに基づいてフォームの将来の開発者に何かを説明する必要はありません。また、フォームを設計するためのトリックを行う必要もありません!私はそれができる最もクリーンなソリューションだと思います(コントロールの再配置を除く)。

もう1つのヒント:

何らかの理由でデザイナーがまだあなたのために働くことを拒否する場合は、コードファイルで_public class Form1 : BaseForm_を_public class Form1 : BaseFormMiddle1_(または_BaseFormMiddle2_)に変更して編集するという簡単なトリックをいつでも実行できます。 VSフォームデザイナを使用して、再度変更します。 間違ったバージョンを忘れてリリースする可能性が低いなので、条件付きコンパイルよりもこのトリックの方が好きです。

2
P.W.

抽象クラスpublic abstract class BaseForm: Formはエラーを与え、デザイナーの使用を避けます。仮想メンバーを使用しました。基本的に、抽象メソッドを宣言する代わりに、可能な限り最小の本体を持つ仮想メソッドを宣言しました。これが私がやったことです:

public class DataForm : Form {
    protected virtual void displayFields() {}
}

public partial class Form1 : DataForm {
    protected override void displayFields() { /* Do the stuff needed for Form1. */ }
    ...
}

public partial class Form2 : DataForm {
    protected override void displayFields() { /* Do the stuff needed for Form2. */ }
    ...
}

/* Do this for all classes that inherit from DataForm. */

DataFormは抽象メンバーdisplayFieldsを持つ抽象クラスであると想定されていたため、抽象化を避けるために、この動作を仮想メンバーで「偽造」しています。デザイナーはもう文句を言うことはなく、すべてがうまく機能します。

これは回避策としてはより読みやすいですが、抽象的ではないため、DataFormのすべての子クラスにdisplayFieldsの実装があることを確認する必要があります。したがって、この手法を使用する場合は注意してください。

2
Gabriel L.

Windowsフォームデザイナーは、フォーム/コントロールの基本クラスのインスタンスを作成し、InitializeComponentの解析結果を適用します。そのため、プロジェクトをビルドしなくても、プロジェクトウィザードで作成されたフォームを設計できます。この動作のため、抽象クラスから派生したコントロールを設計することもできません。

これらの抽象メソッドを実装し、デザイナーで実行されていないときに例外をスローできます。コントロールから派生するプログラマは、基本クラスの実装を呼び出さない実装を提供する必要があります。そうしないと、プログラムがクラッシュします。

1