103

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

リスト内のアイテム(パネルで作成された自作のGUIリスト)の基本クラスと、リストに追加できるデータの種類ごとの継承されたコントロールを使用します。

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

ベースコントロールを抽象としてマークすると、Visual Studio2008Designerはウィンドウの読み込みを拒否します。

ベースコントロールを抽象化してDesignerを動作させる方法はありますか?

4

10 に答える 10

99

私はこれを行う方法がなければならないことを知っていました(そして、これをきれいに行う方法を見つけました)。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

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

MiddleClass.cs

public class MiddleClass : BaseForm
... 

BaseForm.cs

public abstract class BaseForm : Form
... 

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

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

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

于 2010-03-09T01:29:14.790 に答える
81

@smelch、デバッグの場合でも、中間コントロールを作成する必要のない、より良い解決策があります。

私たちが欲しいもの

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

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

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

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。


于 2013-07-15T18:29:32.400 に答える
11

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

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

public class Form1
#if DEBUG  
 : MiddleClass 
#else  
 : BaseForm 
#endif 
于 2010-04-14T18:27:28.980 に答える
5

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

interface Base {....}

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

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

于 2011-01-22T07:17:47.750 に答える
5

TypeDescriptionProviderJuan Carlos Diaz による が機能せず、条件付きコンパイルも好きではないと言う人のために、いくつかのヒントがあります。

まず、フォーム デザイナーでコードの変更を機能させるには、 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 クラスの属性に注意してください。TypeDescriptionProvider次に、と2 つのミドル クラスを宣言するだけですが、心配する必要はありません。これらは目に見えず、 Form1 の開発者には無関係です。最初のものは、抽象メンバーを実装します (そして、基本クラスを非抽象にします)。2 つ目は空です。VS フォーム デザイナーが機能するために必要なだけです。次に、 2 番目の中産階級をTypeDescriptionProviderofに割り当てますBaseForm条件付きコンパイルはありません。

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

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

最初の問題 (私のプロジェクトで他のいくつかの場所で私を悩ませ、通常は「型 X を型 X に変換できません」という例外が発生するため、問題がない可能性があります)。タイプを比較するのではなく、タイプ名(FullName)を比較するTypeDescriptionProviderことで解決しました(以下を参照)。

2番目の問題。基本フォームのコントロールが Form1 クラスで設計できず、サイズ変更後に位置が失われる理由はよくわかりませんが、回避しました (良い解決策ではありません - もっとよく知っている場合は書いてください)。BaseForm の Load イベントから非同期的に呼び出されるメソッドで、BaseForm のボタン (右下隅にあるはず) を正しい位置に手動で移動するBeginInvoke(new Action(CorrectLayout));だけです。ケースはシンプル。

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

于 2014-05-05T12:30:17.947 に答える
3

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

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

于 2012-09-14T10:07:47.090 に答える
1

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

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

于 2009-10-25T14:03:14.840 に答える