Bagaimana saya bisa mendapatkan desainer Visual Studio 2008 Windows Forms untuk membuat formulir yang menerapkan kelas dasar abstrak?

98

Saya terlibat masalah dengan Kontrol yang diwariskan dalam Formulir Windows dan membutuhkan beberapa saran tentang itu.

Saya menggunakan kelas dasar untuk item dalam Daftar (daftar GUI buatan sendiri yang terbuat dari panel) dan beberapa kontrol yang diwariskan untuk setiap jenis data yang dapat ditambahkan ke daftar.

Tidak ada masalah dengan itu, tapi sekarang saya menemukan, bahwa itu akan benar, untuk membuat kontrol-basis kelas abstrak, karena memiliki metode, yang perlu diimplementasikan di semua kontrol yang diwariskan, dipanggil dari kode di dalam base-control, tetapi tidak boleh dan tidak dapat diterapkan di kelas dasar.

Ketika saya menandai basis-kontrol sebagai abstrak, Visual Studio 2008 Designer menolak untuk memuat jendela.

Adakah cara agar Desainer bekerja dengan kontrol dasar yang dibuat abstrak?

Oliver Friedrich
sumber

Jawaban:

97

SAYA TAHU pasti ada cara untuk melakukan ini (dan saya menemukan cara untuk melakukannya dengan rapi). Solusi Sheng persis seperti yang saya pikirkan sebagai solusi sementara, tetapi setelah seorang teman menunjukkan bahwa Formkelas tersebut pada akhirnya diwarisi dari sebuah abstractkelas, kami HARUS menyelesaikannya. Jika mereka bisa, kita bisa.

Kami beralih dari kode ini ke masalah

Form1 : Form

Masalah

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

Di sinilah pertanyaan awal mulai dimainkan. Seperti yang dikatakan sebelumnya, seorang teman menunjukkan bahwa System.Windows.Forms.Formmengimplementasikan kelas dasar yang abstrak. Kami dapat menemukan ...

Bukti solusi yang lebih baik

Dari sini, kami mengetahui bahwa mungkin saja desainer menampilkan kelas yang menerapkan kelas abstrak dasar, hanya saja tidak dapat menunjukkan kelas desainer yang segera menerapkan kelas abstrak dasar. Harus ada maksimal 5 peralihan, tetapi kami menguji 1 lapisan abstraksi dan awalnya menemukan solusi ini.

Solusi Awal

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

Ini benar-benar berfungsi dan perancang membuatnya baik-baik saja, masalah terpecahkan .... kecuali Anda memiliki tingkat warisan tambahan dalam aplikasi produksi Anda yang hanya diperlukan karena ketidakcukupan dalam perancang winforms!

Ini bukan solusi pasti 100% tetapi cukup bagus. Pada dasarnya Anda menggunakan #if DEBUGsolusi yang bagus.

Solusi yang Baik

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

Yang dilakukannya hanyalah menggunakan solusi yang diuraikan dalam "solusi awal", jika berada dalam mode debug. Idenya adalah Anda tidak akan pernah merilis mode produksi melalui build debug dan Anda akan selalu mendesain dalam mode debug.

Desainer akan selalu berjalan melawan kode yang dibangun dalam mode saat ini, jadi Anda tidak dapat menggunakan desainer dalam mode rilis. Namun, selama Anda mendesain dalam mode debug dan melepaskan kode bawaan dalam mode rilis, Anda siap melakukannya.

Satu-satunya solusi pasti adalah jika Anda dapat menguji mode desain melalui arahan preprocessor.

smelch
sumber
3
Apakah formulir Anda dan kelas dasar abstrak memiliki konstruktor no-arg? Karena hanya itu yang harus kita tambahkan agar desainer bekerja untuk menunjukkan bentuk yang diwarisi dari bentuk abstrak.
no
Bekerja dengan baik! Saya pikir saya hanya akan membuat modifikasi yang saya butuhkan ke berbagai kelas yang menerapkan yang abstrak, lalu menghapus temp kelas menengah lagi, dan jika saya perlu membuat lebih banyak modifikasi nanti, saya dapat menambahkannya kembali. Solusinya memang berhasil. Terima kasih!
neminem
1
Solusi Anda bekerja dengan baik. Saya tidak percaya Visual Studio mengharuskan Anda untuk melewati rintangan seperti itu untuk melakukan sesuatu yang begitu umum.
RB Davidson
1
Tetapi jika saya menggunakan middleClass yang bukan merupakan kelas abstrak, maka siapa pun yang mewarisi middleClass tidak perlu lagi menerapkan metode abstrak, ini mengalahkan tujuan penggunaan kelas abstrak sejak awal ... Bagaimana cara menyelesaikannya?
Darius
1
@ ti034 Saya tidak dapat menemukan solusi alternatif. Jadi saya hanya membuat fungsi yang seharusnya abstrak dari middleClass memiliki beberapa nilai default yang dapat dengan mudah mengingatkan saya untuk menimpanya, tanpa harus kompiler membuang kesalahan. Misalnya, jika metode abstrak seharusnya mengembalikan judul halaman, maka saya akan membuatnya mengembalikan string "Silakan ubah judul".
Darius
74

@smelch, Ada solusi yang lebih baik, tanpa harus membuat kontrol tengah, bahkan untuk debug.

Apa yang kita mau

Pertama, mari kita definisikan kelas terakhir dan kelas abstrak dasar.

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

Sekarang yang kita butuhkan hanyalah penyedia Deskripsi .

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);
    }
}

Akhirnya kita hanya menerapkan atribut TypeDescriptionProvider ke kontrol Abastract.

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

Dan itu dia. Tidak diperlukan kontrol tengah.

Dan kelas penyedia dapat diterapkan ke sebanyak mungkin basis Abstrak yang kita inginkan dalam solusi yang sama.

* EDIT * Juga yang berikut ini diperlukan di app.config

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

Terima kasih @ user3057544 untuk sarannya.


jucardi
sumber
1
Ini juga berhasil untuk saya bahwa saya menggunakan CF 3.5 tidak adaTypeDescriptionProvider
Adrian Botor
4
Tidak bisa mendapatkan ini bekerja di VS 2010, meskipun smelch berhasil. Ada yang tahu kenapa?
RobC
5
@RobC Designer agak pemarah karena beberapa alasan. Saya menemukan bahwa setelah menerapkan perbaikan ini saya harus membersihkan solusinya, menutup & meluncurkan kembali VS2010, dan membangun kembali; maka saya akan merancang subclass tersebut.
Oblivious Sage
3
Perlu dicatat bahwa karena perbaikan ini menggantikan instance kelas dasar untuk kelas abstrak, elemen visual yang ditambahkan di Designer untuk kelas abstrak tidak akan tersedia saat mendesain subkelas.
Oblivious Sage
1
Ini berhasil untuk saya, tetapi pertama-tama saya harus memulai kembali VS 2013 setelah membangun proyek. @ObliviousSage - Terima kasih atas perhatiannya; dalam kasus saya saat ini setidaknya ini bukan masalah tetapi masih bagus untuk diperhatikan.
InteXX
10

@Smelch, terima kasih atas jawaban yang membantu, karena saya mengalami masalah yang sama baru-baru ini.

Berikut adalah perubahan kecil pada posting Anda untuk mencegah peringatan kompilasi (dengan meletakkan kelas dasar dalam #if DEBUGarahan pra-prosesor):

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

Saya memiliki masalah serupa tetapi menemukan cara untuk merefaktor berbagai hal untuk menggunakan antarmuka sebagai pengganti kelas dasar abstrak:

interface Base {....}

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

Ini mungkin tidak dapat diterapkan pada setiap situasi, tetapi jika memungkinkan akan menghasilkan solusi yang lebih bersih daripada kompilasi bersyarat.

Jan Hettich
sumber
1
Bisakah Anda memberikan contoh kode yang sedikit lebih lengkap? Saya mencoba untuk memahami desain Anda dengan lebih baik dan saya juga akan menerjemahkannya ke dalam VB. Terima kasih.
InteXX
Saya tahu ini sudah tua tetapi saya menemukan ini adalah solusi yang paling tidak meretas. Karena saya masih ingin antarmuka saya terikat dengan UserControl, saya menambahkan UserControlproperti ke antarmuka dan merujuknya kapan pun saya perlu mengaksesnya secara langsung. Dalam implementasi antarmuka saya, saya memperluas UserControl dan menyetel UserControlproperti kethis
chanban
3

Saya menggunakan solusi dalam jawaban ini untuk pertanyaan lain, yang menautkan artikel ini . Artikel ini merekomendasikan penggunaan TypeDescriptionProviderimplementasi kustom dan konkret dari kelas abstrak. Perancang akan menanyakan penyedia khusus jenis yang akan digunakan, dan kode Anda dapat mengembalikan kelas konkret sehingga perancang senang sementara Anda memiliki kendali penuh atas bagaimana kelas abstrak muncul sebagai kelas konkret.

Pembaruan: Saya menyertakan sampel kode yang terdokumentasi dalam jawaban saya untuk pertanyaan lain itu. Kode di sana berfungsi, tetapi terkadang saya harus melalui siklus bersih / bangun seperti yang tercantum dalam jawaban saya untuk membuatnya berfungsi.

Carl G
sumber
3

Saya punya beberapa tip untuk orang yang mengatakan TypeDescriptionProvideroleh Juan Carlos Diaz tidak berfungsi dan juga tidak menyukai kompilasi bersyarat:

Pertama-tama, Anda mungkin harus me - restart Visual Studio agar perubahan dalam kode Anda berfungsi di desainer formulir (saya harus, membangun kembali sederhana tidak berfungsi - atau tidak setiap waktu).

Saya akan menyajikan solusi saya dari masalah ini untuk kasus Bentuk dasar abstrak. Katakanlah Anda memiliki BaseFormkelas dan Anda ingin formulir apa pun yang didasarkan padanya dapat dirancang (ini akan menjadi Form1). The TypeDescriptionProvidersebagaimana yang disampaikan oleh Juan Carlos Diaz tidak bekerja untuk saya juga. Inilah cara saya membuatnya bekerja, dengan menggabungkannya dengan solusi MiddleClass (oleh smelch), tetapi tanpa#if DEBUG kompilasi bersyarat dan dengan beberapa koreksi:

[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
    }
}

Perhatikan atribut pada kelas BaseForm. Kemudian Anda hanya perlu mendeklarasikan TypeDescriptionProviderdan dua kelas menengah , tetapi jangan khawatir, mereka tidak terlihat dan tidak relevan bagi pengembang Form1 . Yang pertama mengimplementasikan anggota abstrak (dan membuat kelas dasar non abstrak). Yang kedua kosong - itu hanya diperlukan untuk desainer formulir VS untuk bekerja. Kemudian Anda menugaskan kelas menengah kedua ke TypeDescriptionProviderdari BaseForm. Tidak ada kompilasi bersyarat.

Saya mengalami dua masalah lagi:

  • Masalah 1: Setelah mengubah Form1 di desainer (atau beberapa kode) itu memberikan kesalahan lagi (ketika mencoba membukanya lagi di desainer).
  • Masalah 2: Kontrol BaseForm ditempatkan secara tidak benar ketika ukuran Form1 diubah di desainer dan formulir ditutup dan dibuka kembali di desainer formulir.

Masalah pertama (Anda mungkin tidak memilikinya karena itu adalah sesuatu yang menghantui saya dalam proyek saya di beberapa tempat lain dan biasanya menghasilkan pengecualian "Tidak dapat mengubah tipe X ke tipe X"). Saya menyelesaikannya TypeDescriptionProviderdengan membandingkan nama tipe (FullName) daripada membandingkan tipe (lihat di bawah).

Masalah kedua. Saya tidak benar-benar tahu mengapa kontrol formulir dasar tidak dapat dirancang di kelas Form1 dan posisinya hilang setelah diubah ukurannya, tetapi saya telah mengatasinya (bukan solusi yang bagus - jika Anda tahu lebih baik, silakan tulis). Saya hanya memindahkan tombol BaseForm secara manual (yang seharusnya berada di pojok kanan bawah) ke posisi yang benar dalam metode yang dipanggil secara asinkron dari acara Muat BaseForm: BeginInvoke(new Action(CorrectLayout));Kelas dasar saya hanya memiliki tombol "OK" dan "Batal", jadi kasusnya sederhana.

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
{
}

Dan di sini Anda memiliki versi yang sedikit dimodifikasi dari 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);
    }
}

Dan itu dia!

Anda tidak perlu menjelaskan apa pun kepada pengembang formulir di masa mendatang berdasarkan BaseForm Anda dan mereka tidak perlu melakukan trik apa pun untuk merancang formulir mereka! Saya pikir ini adalah solusi paling bersih (kecuali untuk kontrol reposisi).

Satu tip lagi:

Jika karena alasan tertentu perancang masih menolak bekerja untuk Anda, Anda selalu dapat melakukan trik sederhana untuk mengubah public class Form1 : BaseFormke public class Form1 : BaseFormMiddle1(atau BaseFormMiddle2) di file kode, mengeditnya di perancang formulir VS dan kemudian mengubahnya kembali. Saya lebih suka trik ini daripada kompilasi bersyarat karena kecil kemungkinannya untuk melupakan dan merilis versi yang salah .

PW
sumber
1
Ini memecahkan masalah yang saya alami dengan solusi Juan di VS 2013; saat memulai ulang VS kontrol memuat secara konsisten sekarang.
Luke Merrett
3

Saya punya tip untuk solusi Juan Carlos Diaz. Ini bekerja dengan baik untuk saya, tetapi ada beberapa masalah dengannya. Ketika saya memulai VS dan masuk desainer semuanya bekerja dengan baik. Tetapi setelah menjalankan solusi, kemudian berhenti dan keluar dan kemudian mencoba untuk masuk desainer pengecualian muncul lagi dan lagi sampai restart VS. Tetapi saya menemukan solusinya - yang harus dilakukan adalah menambahkan di bawah ini ke app.config Anda

  <appSettings>
   <add key="EnableOptimizedDesignerReloading" value="false" />
  </appSettings>
pengguna3057544
sumber
2

Karena kelas abstrak public abstract class BaseForm: Formmemberikan kesalahan dan menghindari penggunaan perancang, saya datang dengan menggunakan anggota virtual. Pada dasarnya, alih-alih mendeklarasikan metode abstrak, saya mendeklarasikan metode virtual dengan tubuh seminimal mungkin. Inilah yang telah saya lakukan:

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. */

Karena DataFormseharusnya kelas abstrak dengan anggota abstrak displayFields, saya "memalsukan" perilaku ini dengan anggota virtual untuk menghindari abstraksi. Perancang tidak mengeluh lagi dan semuanya berfungsi dengan baik untuk saya.

Ini adalah solusi yang lebih mudah dibaca, tetapi karena ini tidak abstrak, saya harus memastikan bahwa semua kelas anak DataFormmenerapkannya displayFields. Jadi, berhati-hatilah saat menggunakan teknik ini.

Gabriel L.
sumber
Ini yang saya gunakan. Saya baru saja melemparkan NotImplementedException di kelas dasar untuk membuat kesalahan jelas jika dilupakan.
Shaun Rowan
1

Desainer Formulir Windows sedang membuat contoh kelas dasar formulir / kontrol Anda dan menerapkan hasil parse InitializeComponent. Itulah mengapa Anda bisa mendesain formulir yang dibuat oleh wizard proyek bahkan tanpa membangun proyek. Karena perilaku ini, Anda juga tidak dapat mendesain kontrol yang diturunkan dari kelas abstrak.

Anda dapat mengimplementasikan metode abstrak tersebut dan membuat pengecualian jika tidak berjalan di desainer. Pemrogram yang berasal dari kontrol harus menyediakan implementasi yang tidak memanggil implementasi kelas dasar Anda. Jika tidak, program akan macet.

Sheng Jiang 蒋 晟
sumber
kasihan, tapi begitulah cara melakukannya. Berharap cara yang benar untuk melakukan ini.
Oliver Friedrich
Ada cara yang lebih baik, lihat jawaban Smelch
Allen Rice
-1

Anda bisa saja mengkompilasi secara kondisional dalam abstractkata kunci tanpa menempatkan kelas terpisah:

#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
  }

Ini berfungsi asalkan BaseFormtidak memiliki metode abstrak apa pun ( abstractkata kunci karena itu hanya mencegah instance runtime kelas).

Peter Gluck
sumber