c# - usuario - ¿Cómo puedo hacer que el diseñador de Windows Forms de Visual Studio 2008 presente un Formulario que implemente una clase base abstracta?
controles de windows forms c# (10)
Hice un problema con los controles heredados en Windows Forms y necesito algunos consejos al respecto.
Utilizo una clase base para elementos en una Lista (lista de GUI hecha por uno mismo hecha de un panel) y algunos controles heredados que son para cada tipo de datos que podrían agregarse a la lista.
No hubo ningún problema con esto, pero ahora descubrí que sería correcto convertir el control base en una clase abstracta, ya que tiene métodos que deben implementarse en todos los controles heredados, llamados desde el código dentro del base-control, pero no debe ni puede implementarse en la clase base.
Cuando marque el control base como abstracto, Visual Studio 2008 Designer se niega a cargar la ventana.
¿Hay alguna manera de que el Diseñador trabaje con el resumen basado en control de base?
@Smelch, gracias por la útil respuesta, ya que me encontré con el mismo problema recientemente.
A continuación se muestra un pequeño cambio en su publicación para evitar advertencias de compilación (colocando la clase base dentro de la #if DEBUG
preprocesador #if DEBUG
):
public class Form1
#if DEBUG
: MiddleClass
#else
: BaseForm
#endif
@smelch, hay una mejor solución, sin tener que crear un control intermedio, incluso para la depuración.
Lo que nosotros queremos
Primero, definamos la clase final y la clase abstracta base.
public class MyControl : AbstractControl
...
public abstract class AbstractControl : UserControl // Also works for Form
...
Ahora todo lo que necesitamos es un proveedor de descripción .
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);
}
}
Finalmente, solo aplicamos un atributo TypeDescriptionProvider al control Abastract.
[TypeDescriptionProvider(typeof(AbstractControlDescriptionProvider<AbstractControl, UserControl>))]
public abstract class AbstractControl : UserControl
...
Y eso es. No se requiere control medio.
Y la clase de proveedor puede aplicarse a tantas bases abstractas como deseemos en la misma solución.
* EDIT * También se necesita lo siguiente en la aplicación.config
<appSettings>
<add key="EnableOptimizedDesignerReloading" value="false" />
</appSettings>
Gracias @ user3057544 por la sugerencia.
Dado que la clase abstracta de clase abstracta public abstract class BaseForm: Form
arroja un error y evita el uso del diseñador, llegué con el uso de miembros virtuales. Básicamente, en lugar de declarar métodos abstractos, declare métodos virtuales con el mínimo cuerpo posible. Esto es lo que hice:
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. */
Como se suponía que DataForm
era una clase abstracta con el miembro abstracto displayFields
, " displayFields
" este comportamiento con miembros virtuales para evitar la abstracción. El diseñador ya no se queja y todo funciona bien para mí.
Es una solución más legible, pero como no es abstracta, tengo que asegurarme de que todas las clases secundarias de DataForm
tengan su implementación de displayFields
. Por lo tanto, tenga cuidado al usar esta técnica.
El Diseñador de Windows Forms está creando una instancia de la clase base de su formulario / control y aplica el resultado de análisis de InitializeComponent
. Es por eso que puede diseñar el formulario creado por el asistente del proyecto sin siquiera construir el proyecto. Debido a este comportamiento, tampoco puede diseñar un control derivado de una clase abstracta.
Puede implementar esos métodos abstractos y lanzar una excepción cuando no se está ejecutando en el diseñador. El programador que se deriva del control debe proporcionar una implementación que no llame a su implementación de la clase base. De lo contrario, el programa se bloqueará.
Estoy usando la solución en esta respuesta a otra pregunta, que vincula este artículo . El artículo recomienda usar un TypeDescriptionProvider
personalizado y una implementación concreta de la clase abstracta. El diseñador le preguntará al proveedor personalizado qué tipos usar, y su código puede devolver la clase concreta para que el diseñador esté contento mientras usted tiene control total sobre cómo la clase abstracta aparece como una clase concreta.
Actualización: incluí una muestra de código documentado en mi respuesta a esa otra pregunta. El código allí funciona, pero a veces tengo que pasar por un ciclo de limpieza / compilación como se indica en mi respuesta para que funcione.
Puede compilar de forma condicional en la palabra clave abstract
sin interponer una clase separada:
#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
}
Esto funciona siempre que BaseForm
no tenga ningún método abstracto (la palabra clave abstract
por lo tanto, solo evita la creación de instancias en tiempo de ejecución de la clase).
Tuve un problema similar pero encontré una forma de refactorizar las cosas para usar una interfaz en lugar de una clase base abstracta:
interface Base {....}
public class MyUserControl<T> : UserControl, Base
where T : /constraint/
{ ... }
Esto puede no ser aplicable a todas las situaciones, pero cuando es posible da como resultado una solución más limpia que la compilación condicional.
Tengo algunos consejos para las personas que dicen que el TypeDescriptionProvider
de Juan Carlos Díaz no está funcionando y tampoco me gusta la compilación condicional:
Antes que nada, puede que tenga que reiniciar Visual Studio para que los cambios en su código funcionen en el diseñador de formularios (tuve que hacerlo, la reconstrucción simple no funcionó, o no todas las veces).
Presentaré mi solución de este problema para el caso de la Forma base abstracta. Supongamos que tiene una clase BaseForm
y desea que todos los formularios basados en ella sean identificables (esto será Form1
). El TypeDescriptionProvider
presentado por Juan Carlos Díaz tampoco funcionó para mí. Así es cómo lo hice funcionar, uniéndolo a la solución MiddleClass (por smelch), pero sin la #if DEBUG
condicional #if DEBUG
y con algunas correcciones:
[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
}
}
Observe el atributo en la clase BaseForm. Entonces solo tiene que declarar TypeDescriptionProvider
y dos clases medias , pero no se preocupe, son invisibles e irrelevantes para el desarrollador de Form1 . El primero implementa los miembros abstractos (y hace que la clase base no sea abstracta). El segundo está vacío; solo se requiere para que el diseñador de formularios VS funcione. Luego asigna la segunda clase media al TypeDescriptionProvider
de BaseForm
. Sin compilación condicional.
Estaba teniendo dos problemas más:
- Problema 1: después de cambiar Form1 en el diseñador (o algún código), estaba volviendo a generar el error (al intentar abrirlo nuevamente en el diseñador).
- Problema 2: los controles de BaseForm se colocaron incorrectamente cuando se modificó el tamaño del Form1 en el diseñador y el formulario se cerró y se volvió a abrir en el diseñador de formularios.
El primer problema (puede que no lo tengas porque es algo que me atormenta en mi proyecto en algunos otros lugares y por lo general produce una excepción "No se puede convertir el tipo X a tipo X"). Lo resolví en TypeDescriptionProvider
comparando los nombres de tipo (FullName) en lugar de comparar los tipos (ver a continuación).
El segundo problema Realmente no sé por qué los controles de la forma base no son identificables en la clase Form1 y sus posiciones se pierden después del cambio de tamaño, pero lo he solucionado (no es una buena solución; si conoces algo mejor, por favor escribe). Simplemente muevo manualmente los botones de BaseForm (que deben estar en la esquina inferior derecha) a sus posiciones correctas en un método invocado asincrónicamente desde el evento Load del BaseForm: BeginInvoke(new Action(CorrectLayout));
Mi clase base solo tiene los botones "Aceptar" y "Cancelar", por lo que el caso es simple.
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
{
}
Y aquí tienes la versión ligeramente modificada de 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);
}
}
¡Y eso es!
¡No tiene que explicar nada a los futuros desarrolladores de formularios basados en su BaseForm y no tienen que hacer ningún truco para diseñar sus formularios! Creo que es la solución más limpia que puede ser (excepto para el reposicionamiento de controles).
Un consejo más:
Si por alguna razón el diseñador todavía se niega a trabajar para usted, siempre puede hacer el simple truco de cambiar la public class Form1 : BaseForm
a public class Form1 : BaseFormMiddle1
(o BaseFormMiddle2
) en el archivo de código, editándolo en el diseñador de formularios VS y luego cambiándolo de nuevo. Prefiero este truco sobre la compilación condicional porque es menos probable que olvide y libere la versión incorrecta .
Tengo un consejo para la solución de Juan Carlos Díaz. Funciona muy bien para mí, pero fue un problema con eso. Cuando inicio VS e ingreso al diseñador todo funciona bien. Pero después de ejecutar la solución, entonces deténgase y salga de ella y luego intente ingresar al diseñador. La excepción aparece una y otra vez hasta que se reinicia VS. Pero encontré la solución: todo lo que tienes que hacer es agregar debajo de tu app.config
<appSettings>
<add key="EnableOptimizedDesignerReloading" value="false" />
</appSettings>
SABÍA que tenía que haber una manera de hacer esto (y encontré la manera de hacerlo limpiamente). La solución de Sheng es exactamente lo que se me ocurrió como una solución temporal, pero después de que un amigo señaló que la clase Form
finalmente heredó de una clase abstract
, DEBERÍAMOS poder hacer esto. Si pueden hacerlo, podemos hacerlo.
Pasamos de este código al problema
Form1 : Form
Problema
public class Form1 : BaseForm
...
public abstract class BaseForm : Form
Aquí es donde la pregunta inicial entró en juego. Como dije antes, un amigo señaló que System.Windows.Forms.Form
implementa una clase base que es abstracta. Pudimos encontrar ...
Prueba de una mejor solución
Jerarquía de herencia:
- System.Object
- System.MarshalByRefObject (
public **abstract** class MarshalByRefObject
)
- System.MarshalByRefObject (
- System.Object
A partir de esto, sabíamos que era posible para el diseñador mostrar una clase que implementaba una clase abstracta base, simplemente no podía mostrar una clase de diseñador que implementara inmediatamente una clase abstracta base. Tenía que haber un máximo de 5 entremedias, pero probamos 1 capa de abstracción e inicialmente surgió esta solución.
Solución inicial
public class Form1 : MiddleClass
...
public class MiddleClass : BaseForm
...
public abstract class BaseForm : Form
...
Esto realmente funciona y el diseñador lo procesa bien, resuelve el problema ... ¡excepto que tiene un nivel extra de herencia en su aplicación de producción que solo era necesario debido a una inadecuación en el diseñador de las formas de ganar!
Esta no es una solución 100% segura, pero es bastante buena. Básicamente, utilizas #if DEBUG
para encontrar la solución refinada.
Solución refinada
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
...
Lo que hace esto es usar solo la solución descrita en "solución inicial", si está en modo de depuración. La idea es que nunca liberarás el modo de producción a través de una compilación de depuración y que siempre diseñarás en modo de depuración.
El diseñador siempre se ejecutará en contra del código creado en el modo actual, por lo que no podrá usar el diseñador en el modo de lanzamiento. Sin embargo, siempre que diseñe en modo de depuración y libere el código incorporado en el modo de lanzamiento, estará listo.
La única solución infalible sería si puede probar el modo de diseño a través de una directiva de preprocesador.