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

98

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

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

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

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

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

Оливер Фридрих
источник

Ответы:

97

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

Это не стопроцентное решение, но довольно хорошее. В основном вы #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
... 

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

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

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

запах
источник
3
Есть ли у вашей формы и абстрактного базового класса конструктор без аргументов? Потому что это все, что нам нужно было добавить, чтобы дизайнер работал над отображением формы, унаследованной от абстрактной формы.
Сработало отлично! Я полагаю, что просто внесу необходимые изменения в различные классы, реализующие абстрактный, затем снова удалю временный средний класс, и, если мне когда-либо понадобится внести дополнительные изменения позже, я могу добавить его обратно. Обходной путь действительно сработал. Спасибо!
neminem
1
Ваше решение отлично работает. Я просто не могу поверить, что Visual Studio требует, чтобы вы преодолели такие препятствия, чтобы сделать что-то столь распространенное.
РБ Дэвидсон
1
Но если я использую middleClass, который не является абстрактным классом, то тому, кто наследует middleClass, больше не нужно реализовывать абстрактный метод, это в первую очередь противоречит самой цели использования абстрактного класса ... Как это решить?
Дариус
1
@ ti034 Я не нашел обходного пути. Поэтому я просто заставляю предположительно абстрактные функции из среднего класса иметь некоторые значения по умолчанию, которые могут легко напоминать мне переопределить их, не заставляя компилятор выдавать ошибку. Например, если предположительно абстрактный метод должен вернуть заголовок страницы, я заставлю его возвращать строку «Пожалуйста, измените заголовок».
Дариус
74

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


Jucardi
источник
1
Это также сработало для меня, потому что я использую CF 3.5, которого нетTypeDescriptionProvider
Адриан Ботор
4
Не удалось заставить это работать в VS 2010, хотя smelch действительно работал. Кто-нибудь знает почему?
RobC
5
@RobC Designer по какой-то причине сварливый. Я обнаружил, что после внедрения этого исправления мне пришлось очистить решение, закрыть и повторно запустить VS2010 и перестроить; тогда это позволило бы мне разработать подкласс.
Oblivious Sage
3
Стоит отметить, что поскольку это исправление заменяет абстрактный класс экземпляром базового класса, визуальные элементы, добавленные в конструктор для абстрактного класса, не будут доступны при разработке подклассов.
Oblivious Sage
1
Это сработало для меня, но мне сначала пришлось перезапустить VS 2013 после сборки проекта. @ObliviousSage - Спасибо за предупреждение; в моем текущем случае, по крайней мере, это не проблема, но все же стоит остерегаться.
InteXX
10

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

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

public class Form1
#if DEBUG  
 : MiddleClass 
#else  
 : BaseForm 
#endif 
Дэйв Клеммер
источник
5

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

interface Base {....}

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

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

Ян Хеттих
источник
1
Не могли бы вы предоставить более полный пример кода? Я пытаюсь лучше понять ваш дизайн, и я также переведу его на VB. Спасибо.
InteXX
Я знаю, что это устарело, но я обнаружил, что это наименее хакерское решение. Поскольку я все еще хотел, чтобы мой интерфейс был привязан к UserControl, я добавил UserControlсвойство к интерфейсу и указывал на него всякий раз, когда мне нужно было получить к нему прямой доступ. В своих реализациях интерфейса я расширяю UserControl и устанавливаю для UserControlсвойства значениеthis
chanban
3

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

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

Карл Дж.
источник
3

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

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

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

Первая проблема (у вас ее может не быть, потому что это то, что преследует меня в моем проекте еще в нескольких местах и ​​обычно вызывает исключение «Невозможно преобразовать тип X в тип X»). Я решил это в TypeDescriptionProvider, сравнивая имена типов (FullName) вместо сравнения типов (см. Ниже).

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

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

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

PW
источник
1
Это решило проблему, с которой я столкнулся с решением Хуана в VS 2013; при перезапуске VS элементы управления теперь загружаются последовательно.
Люк Меррет,
3

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

  <appSettings>
   <add key="EnableOptimizedDesignerReloading" value="false" />
  </appSettings>
user3057544
источник
2

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

Габриэль Л.
источник
Это то, с чем я пошел. Я просто добавил NotImplementedException в базовый класс, чтобы сделать ошибку очевидной, если она забыта.
Шон Роуэн
1

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

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

Шэн Цзян 蒋 晟
источник
жаль, но так это пока сделано. Надеялся на правильный способ сделать это.
Оливер Фридрих,
Есть способ получше, см. Ответ Смелча
Аллен Райс
-1

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

Питер Глюк
источник