Come posso ottenere il designer Windows Form di Visual Studio 2008 per eseguire il rendering di un modulo che implementa una classe base astratta?


98

Ho riscontrato un problema con i controlli ereditati in Windows Form e ho bisogno di alcuni consigli in merito.

Uso una classe base per gli elementi in una lista (lista GUI autoprodotta composta da un pannello) e alcuni controlli ereditati che sono per ogni tipo di dati che potrebbero essere aggiunti alla lista.

Non c'era problema, ma ora ho scoperto che sarebbe stato giusto rendere il controllo di base una classe astratta, poiché ha metodi, che devono essere implementati in tutti i controlli ereditati, chiamati dal codice all'interno del base-control, ma non deve e non può essere implementato nella classe base.

Quando contrassegno il controllo di base come astratto, la finestra di progettazione di Visual Studio 2008 si rifiuta di caricare la finestra.

C'è un modo per far funzionare il Designer con il controllo di base reso astratto?

Risposte:


97

SAPEVO che doveva esserci un modo per farlo (e ho trovato un modo per farlo in modo pulito). La soluzione di Sheng è esattamente quella che mi è venuta in mente come soluzione temporanea, ma dopo che un amico ha sottolineato che la Formclasse alla fine ha ereditato da una abstractclasse, DOVREMMO essere in grado di farlo. Se possono farlo, possiamo farlo.

Siamo passati da questo codice al problema

Form1 : Form

Problema

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

È qui che entra in gioco la domanda iniziale. Come detto prima, un amico ha sottolineato che System.Windows.Forms.Formimplementa una classe base astratta. Siamo riusciti a trovare ...

Prova di una soluzione migliore

Da questo, sapevamo che era possibile per il designer mostrare una classe che implementava una classe astratta di base, ma semplicemente non poteva mostrare una classe del designer che implementava immediatamente una classe astratta di base. Dovevano esserci al massimo 5 in mezzo, ma abbiamo testato 1 strato di astrazione e inizialmente abbiamo trovato questa soluzione.

Soluzione iniziale

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

Questo funziona effettivamente e il designer lo rende a posto, il problema è risolto ... tranne per il fatto che hai un ulteriore livello di ereditarietà nella tua applicazione di produzione che era necessario solo a causa di un'inadeguatezza nel designer di winforms!

Questa non è una soluzione sicura al 100% ma è abbastanza buona. Fondamentalmente usi #if DEBUGper trovare la soluzione raffinata.

Soluzione raffinata

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

Ciò che fa è utilizzare solo la soluzione descritta in "soluzione iniziale", se è in modalità di debug. L'idea è che non rilascerai mai la modalità di produzione tramite una build di debug e che progetterai sempre in modalità di debug.

Il designer verrà sempre eseguito con il codice creato nella modalità corrente, quindi non è possibile utilizzare il designer in modalità di rilascio. Tuttavia, finché si progetta in modalità di debug e si rilascia il codice incorporato in modalità di rilascio, si è a posto.

L'unica soluzione sicura sarebbe quella di poter testare la modalità di progettazione tramite una direttiva del preprocessore.


3
Il tuo modulo e la classe base astratta hanno un costruttore senza argomenti? Perché questo è tutto ciò che abbiamo dovuto aggiungere per far lavorare il designer per mostrare una forma che ha ereditato da una forma astratta.
n.

Ha funzionato alla grande! Immagino che apporterò solo le modifiche di cui avevo bisogno alle varie classi che implementano quella astratta, quindi rimuoverò di nuovo la classe media temporanea e se mai avrò bisogno di apportare ulteriori modifiche in seguito, posso aggiungerla di nuovo. La soluzione alternativa ha effettivamente funzionato. Grazie!
neminem

1
La tua soluzione funziona alla grande. Non riesco a credere che Visual Studio richieda di saltare attraverso questi cerchi per fare qualcosa di così comune.
RB Davidson,

1
Ma se uso una middleClass che non è una classe astratta, allora chi eredita la middleClass non deve più implementare il metodo abstract, questo vanifica lo scopo stesso di usare la classe astratta in primo luogo ... Come risolverlo?
Darius

1
@ ti034 Non sono riuscito a trovare alcuna soluzione alternativa. Quindi creo solo che le funzioni apparentemente astratte dalla middleClass abbiano alcuni valori predefiniti che possono facilmente ricordarmi di sovrascriverli, senza che il compilatore generi errori. Ad esempio, se il metodo apparentemente astratto è di restituire il titolo della pagina, allora gli farò restituire una stringa "Per favore cambia il titolo".
Darius

74

@smelch, c'è una soluzione migliore, senza dover creare un controllo intermedio, anche per il debug.

Ciò che vogliamo

Per prima cosa, definiamo la classe finale e la classe astratta di base.

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

Ora tutto ciò di cui abbiamo bisogno è un provider di descrizioni .

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

Infine applichiamo un attributo TypeDescriptionProvider al controllo Abastract.

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

E questo è tutto. Nessun controllo intermedio richiesto.

E la classe provider può essere applicata a tutte le basi astratte che vogliamo nella stessa soluzione.

* EDIT * Anche quanto segue è necessario in app.config

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

Grazie @ user3057544 per il suggerimento.



1
Questo ha funzionato anche per me che sto usando CF 3.5, non c'èTypeDescriptionProvider
Adrian Botor

4
Non è stato possibile farlo funzionare in VS 2010, anche se Smelch ha funzionato. Qualcuno sa perché?
RobC

5
@RobC Designer è un po 'scontroso per qualche motivo. Ho scoperto che dopo aver implementato questa correzione ho dovuto pulire la soluzione, chiudere e rilanciare VS2010 e ricostruire; quindi mi consentirebbe di progettare la sottoclasse.
Oblivious Sage

3
Vale la pena notare che poiché questa correzione sostituisce un'istanza della classe base per la classe astratta, gli elementi visivi aggiunti in Designer per la classe astratta non saranno disponibili durante la progettazione delle sottoclassi.
Ignaro Saggio

1
Questo ha funzionato per me, ma prima ho dovuto riavviare VS 2013 dopo aver creato il progetto. @ObliviousSage - Grazie per l'avviso; almeno nel mio caso attuale questo non è un problema, ma è comunque buono a cui prestare attenzione.
InteXX

10

@ Smelch, grazie per l'utile risposta, poiché di recente mi sono imbattuto nello stesso problema.

Di seguito è una piccola modifica al tuo post per evitare avvisi di compilazione (inserendo la classe base all'interno della #if DEBUGdirettiva pre-processore):

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

5

Ho avuto un problema simile ma ho trovato un modo per rifattorizzare le cose per utilizzare un'interfaccia al posto di una classe base astratta:

interface Base {....}

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

Questo potrebbe non essere applicabile a tutte le situazioni, ma quando possibile risulta in una soluzione più pulita rispetto alla compilazione condizionale.


1
Potresti fornire un esempio di codice un po 'più completo? Sto cercando di capire meglio il tuo design e lo tradurrò anche in VB. Grazie.
InteXX

So che questo è vecchio ma ho scoperto che questa era la soluzione meno complicata. Dal momento che volevo ancora la mia interfaccia legata a UserControl, ho aggiunto una UserControlproprietà all'interfaccia e ho fatto riferimento a quella ogni volta che avevo bisogno di accedervi direttamente. Nelle mie implementazioni dell'interfaccia, estendo UserControl e imposto la UserControlproprietà suthis
chanban

3

Sto usando la soluzione in questa risposta a un'altra domanda, che collega questo articolo . L'articolo consiglia di utilizzare un file personalizzatoTypeDescriptionProvider un'implementazione e concreta della classe abstract. Il designer chiederà al provider personalizzato quali tipi utilizzare e il codice può restituire la classe concreta in modo che il progettista sia felice mentre si ha il controllo completo su come la classe astratta appare come classe concreta.

Aggiornamento: ho incluso un esempio di codice documentato nella mia risposta a quell'altra domanda. Il codice lì funziona, ma a volte devo passare attraverso un ciclo di pulizia / compilazione come indicato nella mia risposta per farlo funzionare.


3

Ho alcuni suggerimenti per le persone che dicono che il TypeDescriptionProviderdi Juan Carlos Diaz non funziona e non piace nemmeno la compilazione condizionale:

Prima di tutto, potrebbe essere necessario riavviare Visual Studio affinché le modifiche nel codice funzionino nel progettista del modulo (dovevo farlo, la semplice ricostruzione non funzionava o non ogni volta).

Presenterò la mia soluzione di questo problema per il caso della forma base astratta. Supponiamo che tu abbia una BaseFormclasse e desideri che qualsiasi modulo basato su di esso sia progettabile (questo sarà Form1). Il TypeDescriptionProvidercome presentato da Juan Carlos Diaz non ha funzionato anche per me. Ecco come l'ho fatto funzionare, unendola alla soluzione MiddleClass (di smelch), ma senza la#if DEBUG compilazione condizionale e con alcune correzioni:

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

Notare l'attributo sulla classe BaseForm. Allora non resta che dichiarare le TypeDescriptionProvidere due classi medie , ma non ti preoccupare, sono invisibili e irrilevanti per lo sviluppatore di Form1 . Il primo implementa i membri astratti (e rende la classe base non astratta). Il secondo è vuoto: è necessario solo affinché il progettista di moduli VS funzioni. Quindi assegni la seconda classe media al TypeDescriptionProviderdi BaseForm. Nessuna compilazione condizionale.

Avevo altri due problemi:

  • Problema 1: dopo aver modificato Form1 in designer (o un po 'di codice), l'errore restituiva di nuovo (quando si tentava di aprirlo di nuovo in designer).
  • Problema 2: i controlli di BaseForm venivano posizionati in modo non corretto quando la dimensione del Form1 veniva modificata nella finestra di progettazione e il modulo veniva chiuso e riaperto nuovamente nella finestra di progettazione del modulo.

Il primo problema (potresti non averlo perché è qualcosa che mi perseguita nel mio progetto in pochi altri posti e di solito produce un'eccezione "Impossibile convertire il tipo X in tipo X"). Ho risolto nel TypeDescriptionProviderda confrontando i nomi di tipo (FullName) invece del confronto tra i tipi (vedi sotto).

Il secondo problema. Non so davvero perché i controlli del modulo di base non sono progettabili nella classe Form1 e le loro posizioni vengono perse dopo il ridimensionamento, ma ho risolto il problema (non è una bella soluzione - se ne conosci meglio, scrivi). Ho appena spostato manualmente i pulsanti del BaseForm (che dovrebbero essere nell'angolo in basso a destra) nelle loro posizioni corrette in un metodo richiamato in modo asincrono dall'evento Load del BaseForm: la BeginInvoke(new Action(CorrectLayout));mia classe base ha solo i pulsanti "OK" e "Annulla", quindi il il caso è semplice.

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

E qui hai la versione leggermente modificata di 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);
    }
}

E questo è tutto!

Non devi spiegare nulla ai futuri sviluppatori di moduli basati sul tuo BaseForm e non devono fare alcun trucco per progettare i loro moduli! Penso che sia la soluzione più pulita che può essere (tranne per il riposizionamento dei controlli).

Un altro consiglio:

Se per qualche motivo il progettista si rifiuta ancora di lavoro per voi, si può sempre fare il semplice trucco di cambiare il public class Form1 : BaseFormverso public class Form1 : BaseFormMiddle1(o BaseFormMiddle2) nel file di codice, modificarlo nella forma VS progettista e poi cambiare di nuovo. Preferisco questo trucco alla compilazione condizionale perché è meno probabile che dimentichi e rilasci la versione sbagliata .


1
Questo ha risolto il problema che avevo con la soluzione di Juan in VS 2013; al riavvio di VS i controlli vengono caricati in modo coerente ora.
Luke Merrett

3

Ho un suggerimento per la soluzione di Juan Carlos Diaz. Funziona benissimo per me, ma è stato qualche problema con esso. Quando avvio VS ed entro in designer tutto funziona bene. Ma dopo aver eseguito la soluzione, quindi interromperla e chiuderla e quindi provare ad accedere a designer, l'eccezione appare ancora e ancora fino al riavvio di VS. Ma ho trovato la soluzione: tutto ciò che devi fare è aggiungere di seguito al tuo app.config

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

2

Poiché la classe astratta public abstract class BaseForm: Formdà un errore ed evita l'uso del designer, sono arrivato con l'uso di membri virtuali. Fondamentalmente, invece di dichiarare metodi astratti, ho dichiarato metodi virtuali con il corpo minimo possibile. Ecco cosa ho fatto:

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

Poiché DataFormdoveva essere una classe astratta con il membro abstract displayFields, ho "simulato" questo comportamento con i membri virtuali per evitare l'astrazione. Il designer non si lamenta più e tutto funziona bene per me.

È una soluzione alternativa più leggibile, ma poiché non è astratta, devo assicurarmi che tutte le classi figlie di DataFormabbiano la loro implementazione di displayFields. Quindi, fai attenzione quando usi questa tecnica.


Questo è quello con cui sono andato. Ho appena lanciato NotImplementedException nella classe base per rendere ovvio l'errore se viene dimenticato.
Shaun Rowan

1

Progettazione Windows Form sta creando un'istanza della classe base del modulo / controllo e applica il risultato dell'analisi di InitializeComponent. Ecco perché puoi progettare il modulo creato dalla procedura guidata del progetto senza nemmeno costruire il progetto. A causa di questo comportamento non è inoltre possibile progettare un controllo derivato da una classe astratta.

È possibile implementare questi metodi astratti e generare un'eccezione quando non è in esecuzione nella finestra di progettazione. Il programmatore che deriva dal controllo deve fornire un'implementazione che non chiama l'implementazione della classe base. Altrimenti il ​​programma andrebbe in crash.


peccato, ma è così che è ancora fatto. Speravo in un modo corretto per farlo.
Oliver Friedrich

C'è un modo migliore, vedi la risposta di Smelch
Allen Rice

-1

Potresti semplicemente compilare in modo condizionale la abstractparola chiave senza interporre una classe separata:

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

Funziona a condizione che BaseFormnon disponga di metodi astratti (la abstractparola chiave quindi impedisce solo l'istanza di runtime della classe).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.