Как я могу заставить Visual Studio 2008 Window Forms создать форму, которая реализует абстрактный базовый класс?

Я занимался проблемой с унаследованными элементами управления в Windows Forms и нуждался в советах.

Я использую базовый класс для элементов в списке (собственный список GUI из панели) и некоторые унаследованные элементы управления, которые относятся к каждому типу данных, которые могут быть добавлены в список.

С этим не было никаких проблем, но теперь я узнал, что было бы правильно сделать базовый контроль абстрактным классом, поскольку он имеет методы, которые должны быть реализованы во всех унаследованных элементах управления, вызываемых из код внутри базового элемента управления, но не должен и не может быть реализован в базовом классе.

Когда я отмечаю базовый элемент управления как абстрактный, конструктор Visual Studio 2008 отказывается загружать окно.

Есть ли способ заставить конструктора работать с абстрактным базовым контролем?

Ответ 1

Я ЗНАЛ, что должен был быть способ сделать это (и я нашел способ сделать это чисто). Решение 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
... 

В этом случае используется только решение, описанное в "первоначальном решении", если оно находится в режиме отладки. Идея состоит в том, что вы никогда не будете выпускать производственный режим с помощью сборки отладки и что вы всегда будете разрабатывать в режиме отладки.

Дизайнер всегда будет работать против кода, созданного в текущем режиме, поэтому вы не можете использовать конструктор в режиме выпуска. Тем не менее, до тех пор, пока вы разрабатываете в режиме отладки и выпускаете код, созданный в режиме деблокирования, вам хорошо идти.

Единственное верное решение было бы, если бы вы могли проверить режим разработки с помощью директивы препроцессора.

Ответ 2

@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 за предложение.


Ответ 3

@Smelch, спасибо за полезный ответ, поскольку я недавно сталкивался с той же проблемой.

Ниже перечислены незначительные изменения в вашем сообщении, чтобы предотвратить предупреждения компиляции (путем размещения базового класса в директиве pre-processor #if DEBUG):

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

Ответ 4

У меня была аналогичная проблема, но я нашел способ рефакторинга вещей использовать интерфейс вместо абстрактного базового класса:

interface Base {....}

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

Это может быть неприменимо к каждой ситуации, но по возможности это приводит к более чистому решению, чем к условной компиляции.

Ответ 5

Я использую решение в этом ответе на другой вопрос, который связывает эту статью. В статье рекомендуется использовать пользовательскую TypeDescriptionProvider и конкретную реализацию абстрактного класса. Дизайнер попросит использовать пользовательский поставщик, который будет использовать, и ваш код может вернуть конкретный класс, чтобы дизайнер был счастлив, пока вы полностью контролируете, как абстрактный класс отображается как конкретный класс.

Обновление: я включил документированный пример кода в мой ответ на этот другой вопрос. Код там работает, но иногда мне приходится проходить цикл clean/build, как указано в моем ответе, чтобы заставить его работать.

Ответ 6

У меня есть совет для решения Хуана Карлоса Диаса. Он отлично работает для меня, но с ним некоторые проблемы. Когда я запускаю VS и вхожу дизайнер, все работает отлично. Но после запуска решения остановите и выйдите из него, а затем попробуйте ввести конструктор. Исключение появляется снова и снова до перезапуска VS. Но я нашел решение для него - все, что нужно сделать, это добавить ниже к вашему app.config

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

Ответ 7

Поскольку абстрактный класс 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. Таким образом, будьте осторожны при использовании этой техники.

Ответ 8

У меня есть несколько советов для людей, которые говорят, что TypeDescriptionProvider Хуан Карлос Диаз не работает и не любит условную компиляцию:

Прежде всего, вам может потребоваться перезапустить 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 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 abstract method
    }
}

Обратите внимание на атрибут класса BaseForm. Тогда вам просто нужно объявить TypeDescriptionProvider и два средних класса, но не волнуйтесь, они невидимы и не имеют отношения к разработчику Form1. Первый реализует абстрактные элементы (и делает базовый класс не абстрактным). Второй пуст - он просто необходим для работы дизайнера VS. Затем вы назначаете средний класс второй для TypeDescriptionProvider BaseForm. Нет условной компиляции.

У меня было еще две проблемы:

  • Проблема 1: После изменения Form1 в дизайнере (или в некотором коде) он снова выдавал ошибку (при попытке снова открыть его в дизайнере).
  • Проблема 2: Элементы управления BaseForm были неправильно установлены, когда размер Form1 был изменен в дизайнере, и форма была закрыта и снова открыта в конструкторе форм.

Первая проблема (возможно, у вас ее нет, потому что это то, что преследует меня в моем проекте в нескольких других местах и ​​обычно создает исключение "Can not convert type X to type X" ). Я решил в TypeDescriptionProvider сопоставить имена типов (FullName) вместо сравнения типов (см. Ниже).

Вторая проблема. Я действительно не знаю, почему элементы базовой формы не могут быть назначены в классе Form1, и их позиции теряются после изменения размера, но я работал над ним (не хорошее решение - если вы знаете лучше, напишите). Я просто вручную перемещаю кнопки BaseForm (который должен находиться в нижнем правом углу) в их правильные позиции в методе, вызываемом асинхронно из события загрузки 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, и им не нужно делать никаких трюков для разработки своих форм! Я думаю, что это самое чистое решение, которое может быть (за исключением перестановки элементов управления).

Еще один совет:

Если по какой-то причине дизайнер по-прежнему отказывается работать на вас, вы всегда можете сделать простой трюк об изменении public class Form1 : BaseForm на public class Form1 : BaseFormMiddle1 (или BaseFormMiddle2) в файле кода, отредактировав его в форме VS дизайнера, а затем снова изменить его. Я предпочитаю этот трюк по условной компиляции, потому что менее вероятно забыть и выпустить неправильную версию.

Ответ 9

Дизайнер Windows Forms создает экземпляр базового класса вашей формы/элемента управления и применяет результат синтаксического анализа InitializeComponent. Именно поэтому вы можете создать форму, созданную мастером проекта, даже не создав проект. Из-за этого поведения вы также не можете создать элемент управления, полученный из абстрактного класса.

Вы можете реализовать эти абстрактные методы и выдать исключение, если оно не работает в дизайнере. Программист, получающий контроль, должен предоставить реализацию, которая не вызывает реализацию базового класса. В противном случае программа потерпит крах.

Ответ 10

Вы можете просто скомпилировать ключевое слово 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 только предотвращает создание экземпляра класса выполнения).