Как я могу заставить дизайнер Visual Studio 2008 Windows Forms визуализировать форму, которая реализует абстрактный базовый класс? - PullRequest
96 голосов
/ 25 октября 2009

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

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

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

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

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

Ответы [ 10 ]

94 голосов
/ 09 марта 2010

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

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

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

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

71 голосов
/ 15 июля 2013

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


10 голосов
/ 14 апреля 2010

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

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

public class Form1
#if DEBUG  
 : MiddleClass 
#else  
 : BaseForm 
#endif 
5 голосов
/ 22 января 2011

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

interface Base {....}

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

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

3 голосов
/ 10 января 2015

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

  <appSettings>
   <add key="EnableOptimizedDesignerReloading" value="false" />
  </appSettings>
3 голосов
/ 14 сентября 2012

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

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

2 голосов
/ 05 мая 2014

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

2 голосов
/ 19 января 2014

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

1 голос
/ 25 октября 2009

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

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

0 голосов
/ 18 октября 2013

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

...