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


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

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

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

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

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

10 95

10 ответов:

Я знал, что должен быть способ сделать это (и я нашел способ сделать это аккуратно). Решение Sheng-это именно то, что я придумал в качестве временного обходного пути, но после того, как друг указал, что Form класс в конечном итоге наследуется от abstract класс, мы должны быть в состоянии сделать это. Если они могут это сделать, то и мы можем.

мы перешли от этого кода к проблеме

Form1 : Form
.Объект

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

и это все. Не контроль требуемый.

и класс provider может быть применен к стольким абстрактным базам, сколько мы хотим в том же решении.

* EDIT* Также в приложении необходимо следующее.конфигурации

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

спасибо @user3057544 за предложение.


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

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

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

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

interface Base {....}

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

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

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

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

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

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

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

у меня есть несколько советов для людей, которые говорят 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
    }
}

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

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

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

первая проблема (вы не можете иметь его потому что это преследует меня в моем проекте в нескольких других местах и обычно создает исключение "не удается преобразовать тип X в тип X"). Я решил это в TypeDescriptionProvider by сравнение имен типов (FullName) вместо сравнения типов (см. ниже).

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

и это все!

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

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

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

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

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

вы могли бы просто условно скомпилировать в 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 не имеет никаких абстрактных методов ( предотвращает только создание экземпляра класса во время выполнения).