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


98

Windowsフォームの継承されたコントロールに問題が発生したため、アドバイスが必要です。

リスト(パネルで作成された自作の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
... 

これが行うことは、デバッグモードの場合、「初期ソリューション」で概説されているソリューションのみを使用することです。アイデアは、デバッグビルドを介して本番モードをリリースすることはなく、常にデバッグモードで設計することです。

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

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


3
フォームと抽象基本クラスには引数のないコンストラクタがありますか?抽象フォームから継承されたフォームをデザイナーが機能させるために追加する必要があるのはそれだけです。

よくできました!抽象クラスを実装するさまざまなクラスに必要な変更を加え、temp中間クラスを再度削除します。後でさらに変更が必要になった場合は、再度追加できます。回避策は確かに機能しました。ありがとう!
neminem

1
あなたのソリューションは素晴らしい働きをします。Visual Studioでは、そのようなフープを飛ばして、非常に一般的なことを行う必要があるとは信じられません。
RB Davidson

1
しかし、抽象クラスではないmiddleClassを使用する場合、middleClassを継承する人は、抽象メソッドを実装する必要がなくなります。これにより、最初に抽象クラスを使用する目的そのものが無効になります。これを解決するにはどうすればよいですか?
ダライアス

1
@ ti034回避策が見つかりませんでした。そのため、middleClassの抽象関数と思われる関数に、コンパイラーにエラーをスローさせることなく、それらをオーバーライドすることを簡単に思い出せるいくつかのデフォルト値を設定します。たとえば、おそらく抽象的なメソッドがページのタイトルを返すことである場合、「タイトルを変更してください」という文字列を返すようにします。
ダライアス

74

@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
...

以上です。ミドルコントロールは必要ありません。

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

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

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

提案をありがとう@ user3057544。



1
また、これは私がCF 3.5を使用していても何があることを私のために動作しませんでしたTypeDescriptionProvider
エイドリアンBotor

4
VS 2010ではこれを機能させることができませんでしたが、smelchは機能しました。誰もがなぜ知っていますか?
RobC 2014

5
@RobCデザイナーは、何らかの理由で不機嫌そうです。この修正を実装した後、ソリューションをクリーンアップし、VS2010を閉じて再起動し、再構築する必要があることがわかりました。次に、サブクラスを設計します。
Oblivious Sage 2015

3
この修正により、抽象クラスが基本クラスのインスタンスに置き換えられるため、サブクラスを設計するときに、抽象クラスのデザイナーに追加された視覚要素を使用できないことに注意してください。
Oblivious Sage 2015

1
これでうまくいきましたが、プロジェクトをビルドした後、VS 2013を再起動する必要がありました。@ObliviousSage-ヘッズアップありがとうございます。私の現在のケースでは、少なくともこれは問題ではありませんが、それでも注意する必要があります。
InteXX、2015

10

@Smelch、私が最近同じ問題に遭遇していたので、役立つ回答をありがとう。

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

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

5

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

interface Base {....}

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

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


1
もう少し完全なコードサンプルを提供できますか?私はあなたのデザインをよりよく理解しようとしています、そしてそれをVBに翻訳するつもりです。ありがとう。
InteXX、2015

私はこれが古いことを知っていますが、これが最もハッキングの少ないソリューションであることがわかりました。私はまだインターフェイスをUserControlに関連付けたいと思っていたので、インターフェイスにUserControlプロパティを追加し、直接アクセスする必要があるときはいつでもそれを参照しました。私のインターフェース実装では、UserControlを拡張し、UserControlプロパティをthis
chanbanに

3

私は、この記事にリンクしている別の質問に対するこの回答のソリューションを使用しています。この記事では、抽象クラスのカスタムで具体的な実装の使用を推奨しています。デザイナーはカスタムプロバイダーに使用する型を尋ね、コードは具象クラスを返すことができるため、抽象クラスが具象クラスとしてどのように表示されるかを完全に制御している間、デザイナーは満足できます。TypeDescriptionProvider

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


3

TypeDescriptionProviderJuan Carlos Diazが機能しておらず、条件付きコンパイルも好きではないという人のためのヒントをいくつか紹介します。

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

抽象ベースフォームの場合のこの問題の解決策を紹介します。あなたが持っていると言うBaseFormクラスを、あなたは(これは設計可能になりますように、それに基づいて任意のフォームをしたいですForm1)。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フォームデザイナが機能するために必要なだけです。次に、2番目の中間クラスをTypeDescriptionProviderofに割り当てますBaseForm条件付きコンパイルはありません。

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

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

最初の問題(プロジェクトの別の場所で悩まされ、通常、「タイプXをタイプXに変換できない」例外が発生するため、問題がない可能性があります)。私はそれを解決するTypeDescriptionProviderことにより、タイプ名の比較(下記参照)(フルネーム)の代わりの種類を比較します。

第二の問題。ベースフォームのコントロールがForm1クラスでデザインできず、サイズ変更後にそれらの位置が失われる理由は本当にわかりませんが、私はそれを回避しました(良い解決策ではありません-ご存知の場合は、書き込んでください)。BaseFormのLoadイベントから非同期的に呼び出されるメソッドの正しい位置に、BaseFormのボタン(右下隅にある必要があります)を手動で移動します。BeginInvoke(new Action(CorrectLayout));私の基本クラスには「OK」ボタンと「Cancel」ボタンしかないため、ケースはシンプルです。

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 : BaseFormpublic class Form1 : BaseFormMiddle1(またはBaseFormMiddle2)に変更し、VSフォームデザイナーで編集してから再び変更するという簡単なトリックをいつでも実行できます。間違ったバージョンを忘れてリリースする可能性が低いため、条件付きコンパイルよりもこのトリックをお勧めします。


1
これにより、VS 2013でのJuanのソリューションで抱えていた問題が解決しました。VSを再起動すると、コントロールが一貫して読み込まれます。
ルークメレット

3

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

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

2

抽象クラス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ます。したがって、この手法を使用するときは注意してください。


これは私が行ったものです。忘れてしまった場合にエラーを明確にするために、基本クラスにNotImplementedExceptionをスローしました。
Shaun Rowan

1

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

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


残念ですが、それはまだそれが行われている方法です。これを行う正しい方法を望んでいました。
Oliver Friedrich

もっと良い方法があります、スメルチの答えを見てください
アレンライス

-1

abstract別のクラスを挿入せずに、条件付きでキーワードをコンパイルできます。

#if DEBUG
  // Visual Studio 2008 designer complains when a form inherits from an 
  // abstract base class
  public class BaseForm: Form {
#else
  // For production do it the *RIGHT* way.
  public abstract class BaseForm: Form {
#endif

    // Body of BaseForm goes here
  }

これBaseFormは、抽象メソッドがない場合に機能します(abstractしたがって、キーワードは、クラスの実行時のインスタンス化を防止するだけです)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.