Як я можу змусити дизайнера Windows Forms Visual Studio 2008 надати форму, яка реалізує абстрактний базовий клас?


98

Я зіткнувся з проблемою з успадкованими елементами управління в Windows Forms і мені потрібен поради щодо цього.

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

Проблеми з цим не було, але я тепер з’ясував, що було б правильно зробити базовий контроль абстрактним класом, оскільки він має методи, які потрібно реалізувати у всіх успадкованих елементах управління, викликаних з коду всередині base-control, але не повинен і не може бути реалізований у базовому класі.

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

Чи є спосіб змусити роботу конструктора з абстрактним базовим контролем?

Відповіді:


97

Я знаю, що це було, як це зробити (і я знайшов спосіб зробити це чисто). Рішення Шен - це саме те, що я придумав як тимчасове вирішення, але після того, як друг зазначив, що 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
... 

Це робиться лише використовувати рішення, викладене в "початковому рішенні", якщо воно знаходиться в режимі налагодження. Ідея полягає в тому, що ви ніколи не випустите режим виробництва за допомогою налагодження і ви завжди будете проектувати в режимі налагодження.

Дизайнер завжди буде працювати з кодом, вбудованим у поточному режимі, тому ви не можете використовувати конструктор у режимі випуску. Однак, поки ви проектуєте в режимі налагодження та випускаєте код, вбудований у режимі випуску, вам добре йти.

Єдиним надійним рішенням буде те, якщо ви можете протестувати режим дизайну за допомогою директиви препроцесора.


3
Чи мають ваша форма та абстрактний базовий клас конструктор без аргументів? Тому що це все, що нам довелося додати, щоб дизайнер працював над тим, щоб показати форму, успадковану від абстрактної форми.
нос

Працювали чудово! Я думаю, що я просто внесу необхідні зміни до різних класів, що реалізують абстрактний, а потім знову видаляю середній клас temp, і якщо мені пізніше доведеться вносити більше модифікацій, я можу додати його назад. Дійсне рішення справді спрацювало. Дякую!
neminem

1
Ваше рішення чудово працює. Я просто не можу повірити, що Visual Studio вимагає від вас перестрибувати такі обручі, щоб зробити щось таке звичайне.
RB Davidson

1
Але якщо я використовую MiddleClass, який не є абстрактним класом, то той, хто успадковує midClass, більше не повинен реалізовувати абстрактний метод, це передує саму мету використання абстрактного класу в першу чергу ... Як це вирішити?
Дарій

1
@ ti034 Я не зміг знайти жодного вирішення. Тому я просто роблю нібито абстрактні функції з MiddleClass мають деякі значення за замовчуванням, які можуть легко нагадати мені їх переосмислити, не маючи компілятора, щоб кидати помилки. Наприклад, якщо передбачувано абстрактним методом є повернення заголовку сторінки, я змушу його повернути рядок "Будь ласка, змініть заголовок".
Дарій

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 до елемента Абстрактний елемент.

[TypeDescriptionProvider(typeof(AbstractControlDescriptionProvider<AbstractControl, UserControl>))]
public abstract class AbstractControl : UserControl
...

І це все. Середній контроль не потрібен.

І клас провайдера може бути застосований до стільки абстрактних баз, скільки ми хочемо в одному і тому ж рішенні.

* EDIT * Також у програмі app.config потрібне наступне

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

Дякуємо @ user3057544 за пропозицію.



1
Це також спрацювало для мене, що я використовую CF 3.5 немаєTypeDescriptionProvider
Адріан Ботор

4
Не вдалося зробити це роботою у VS 2010, хоча смерд працював. Хтось знає, чому?
RobC

5
@RobC Дизайнер чомусь бурхливий чомусь. Я виявив, що після впровадження цього виправлення мені довелося очистити рішення, закрити та перезапустити VS2010 та відновити його; тоді це дозволило б мені спроектувати підклас.
Очевидний мудрець

3
Варто зазначити, що оскільки це виправлення замінює екземпляр базового класу абстрактного класу, візуальні елементи, додані в конструктор для абстрактного класу, не будуть доступні при проектуванні підкласів.
Очевидний мудрець

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


1
Це вирішило питання, яке було у мене з рішенням Хуана у VS 2013; при перезапуску VS елементи керування завантажуються постійно.
Люк Меррет

3

У мене є порада щодо рішення Хуана Карлоса Діаса. Це чудово працює для мене, але з цим була якась проблема. Коли я запускаю VS і ввожу дизайнера, все працює добре. Але після запуску рішення, тоді зупиніть і вийдіть з нього, а потім спробуйте ввести дизайнер, виняток з’являється знову і знову до перезавантаження VS. Але я знайшов рішення для цього - все, що потрібно зробити, - це додати нижче до своєї програми.config

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

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 в базовий клас, щоб зробити помилку очевидною, якщо її забули.
Shaun Rowan

1

Дизайнер форм Windows створює екземпляр базового класу форми / елемента управління і застосовує результат аналізу 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ключове слово, таким чином, лише запобігає виконанню класу для виконання).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.