Jak uzyskać Visual Studio 2008 Windows Forms designer do renderowania formularza implementującego abstrakcyjną klasę bazową?

Mam problem z dziedziczonymi kontrolkami w Windows Forms i potrzebuję na ten temat Porady.

Używam klasy bazowej dla elementów na liście (selfmade GUI lista wykonana z panelu) i niektóre odziedziczone kontrolki, które są dla każdego typu danych, które mogą być dodane do listy.

Nie było z tym problemu, ale teraz dowiedziałem się, że byłoby dobrze, aby base-control była klasą abstrakcyjną, ponieważ ma metody, które muszą być zaimplementowane we wszystkich odziedziczonych kontrolkach, wywołanych z kod wewnątrz base-control, ale nie może i nie może być zaimplementowany w klasie bazowej.

Kiedy oznaczam base-control jako abstrakcyjny, projektant Visual Studio 2008 odmawia załadowania okna.

Czy istnieje sposób, aby praca projektanta z kontrolą bazy stała się abstrakcyjna?

Author: Peter Mortensen, 2009-10-25

10 answers

Wiedziałem, że musi być jakiś sposób, aby to zrobić (i znalazłem sposób, aby to zrobić czysto). Rozwiązanie Shenga jest dokładnie tym, co wymyśliłem jako tymczasowe obejście, ale po tym, jak przyjaciel wskazał, że klasa Form ostatecznie odziedziczona po klasie abstract, powinniśmy być w stanie to zrobić. Jeśli oni to zrobią, my też.

Przeszliśmy od tego kodu do problemu

Form1 : Form

Problem

public class Form1 : BaseForm
...
public abstract class BaseForm : Form

Tutaj pojawiło się pierwsze pytanie. Jak wspomniano wcześniej, a friend zauważył, że System.Windows.Forms.Form implementuje klasę bazową, która jest abstrakcyjna. Udało nam się znaleźć...

Dowód na lepsze rozwiązanie

Z tego wiedzieliśmy, że projektant może pokazać klasę to zaimplementowało podstawową klasę abstrakcyjną, po prostu nie mogło pokazać klasy projektanta, która natychmiast zaimplementowała podstawową klasę abstrakcyjną. Musiało być max 5 inbetween, ale przetestowaliśmy 1 warstwę abstrakcji i początkowo wymyśliliśmy To rozwiązanie.

Roztwór Początkowy

public class Form1 : MiddleClass
...
public class MiddleClass : BaseForm
... 
public abstract class BaseForm : Form
... 

To rzeczywiście działa i projektant renderuje to dobrze, problem rozwiązany.... z tym, że masz dodatkowy poziom dziedziczenia w aplikacji produkcyjnej, który był konieczny tylko ze względu na nieadekwatność w projektant winforms!

To nie jest w 100% pewne rozwiązanie, ale jest całkiem dobre. Zasadniczo używasz #if DEBUG, aby wymyślić wyrafinowane rozwiązanie.

Rafinowany Roztwór

Form1.cs

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

Cs

public class MiddleClass : BaseForm
... 

Forma podstawowa.cs

public abstract class BaseForm : Form
... 

To, co robi, to używać rozwiązania opisanego w "initial solution", jeśli jest w trybie debugowania. Chodzi o to, że nigdy nie wypuścisz trybu produkcyjnego za pomocą debugowania i że zawsze będziesz projektować w trybie debugowania.

Projektant zawsze będzie działał zgodnie z kodem zbudowanym w bieżącym trybie, więc nie można go używać w trybie release. Jednak tak długo, jak projektujesz w trybie debugowania i uwalniasz kod zbudowany w trybie wydania, możesz iść.

Jedynym niezawodnym rozwiązaniem byłoby przetestowanie trybu projektowania za pomocą dyrektywy preprocesora.

 91
Author: smelch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-09 03:58:50

@ smelch, jest lepsze rozwiązanie, bez konieczności tworzenia Środkowej kontroli, nawet do debugowania.

Czego chcemy

Najpierw zdefiniujmy klasę końcową i podstawową klasę abstrakcyjną.

public class MyControl : AbstractControl
...
public abstract class AbstractControl : UserControl // Also works for Form
...

Teraz potrzebujemy tylko dostawcy opisu .

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);
    }
}

Na koniec po prostu zastosujemy atrybut TypeDescriptionProvider do kontrolki Abastract.

[TypeDescriptionProvider(typeof(AbstractControlDescriptionProvider<AbstractControl, UserControl>))]
public abstract class AbstractControl : UserControl
...
I to wszystko. Nie jest wymagana Środkowa Kontrola.

I klasa provider może być stosowana do tyle abstrakcyjnych podstaw, ile chcemy w tym samym rozwiązaniu.

* EDIT * W aplikacji potrzebne są również następujące elementy.config

<appSettings>
    <add key="EnableOptimizedDesignerReloading" value="false" />
</appSettings>
Dzięki @user3057544 za sugestię.
 70
Author: Juan Carlos Diaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-25 00:37:45

@Smelch, dzięki za pomocną odpowiedź, ponieważ ostatnio natknąłem się na ten sam problem.

Poniżej znajduje się drobna zmiana w Twoim poście, aby zapobiec ostrzeżeniom kompilacji (umieszczając klasę bazową w dyrektywie #if DEBUG pre-processor):

public class Form1
#if DEBUG  
 : MiddleClass 
#else  
 : BaseForm 
#endif 
 10
Author: Dave Clemmer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-06 01:51:50

Miałem podobny problem, ale znalazłem sposób na refaktoryzację rzeczy, aby użyć interfejsu zamiast abstrakcyjnej klasy bazowej:

interface Base {....}

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

Może to nie mieć zastosowania w każdej sytuacji, ale jeśli to możliwe, skutkuje czystszym rozwiązaniem niż kompilacja warunkowa.

 5
Author: Jan Hettich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-22 07:17:47

Używam rozwiązania w tej odpowiedzi {[3] } na inne pytanie, które łączy Ten artykuł . Artykuł zaleca użycie niestandardowej TypeDescriptionProvider i konkretnej implementacji klasy abstrakcyjnej. Projektant zapyta dostawcę niestandardowego, jakich typów użyć, a Twój kod może zwrócić klasę concrete, aby projektant był zadowolony, podczas gdy Ty masz pełną kontrolę nad tym, jak klasa abstrakcyjna wygląda jako klasa concrete.

Update: dodałem udokumentowaną próbkę kodu w mojej odpowiedzi na to drugie pytanie. Kod tam działa, ale czasami muszę przejść przez cykl czyszczenia / budowania, jak wspomniano w mojej odpowiedzi, aby go uruchomić.

 3
Author: Carl G,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:03:09

Mam wskazówkę dla rozwiązania Juana Carlosa Diaza.Dla mnie działa świetnie, ale był z tym jakiś problem. Kiedy uruchamiam VS i Wchodzę designer wszystko działa dobrze. Ale po uruchomieniu rozwiązania, następnie zatrzymać i zamknąć go, a następnie spróbować wprowadzić projektant wyjątek pojawia się ponownie i ponownie aż do ponownego uruchomienia VS. Ale znalazłem rozwiązanie dla niego-wszystko, co zrobić, to dodać poniżej do aplikacji.config

  <appSettings>
   <add key="EnableOptimizedDesignerReloading" value="false" />
  </appSettings>
 3
Author: user3057544,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-10 11:51:02

Ponieważ klasa abstrakcyjna public abstract class BaseForm: Form daje błąd i pozwala uniknąć użycia designera, przyszedłem z użyciem wirtualnych członków. Zasadniczo, zamiast deklarować metody abstrakcyjne, zadeklarowałem metody wirtualne z minimalnym ciałem, jak to możliwe. Oto co zrobiłem:

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. */

Ponieważ DataForm miała być klasą abstrakcyjną z członkiem abstrakcyjnym displayFields, "podrabiam" to zachowanie wirtualnymi członkami, aby uniknąć abstrakcji. Projektant już nie narzeka i wszystko działa dobrze dla mnie.

Jest to obejście bardziej czytelne, ale ponieważ nie jest abstrakcyjne, muszę się upewnić, że wszystkie klasy potomne DataForm mają swoją implementację displayFields. Dlatego należy zachować ostrożność podczas korzystania z tej techniki.

 2
Author: Gabriel L.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-19 18:43:24

Mam kilka wskazówek dla osób, które mówią, że TypeDescriptionProvider autorstwa Juana Carlosa Diaza nie działa i nie podoba mi się kompilacja warunkowa:

Po Pierwsze, być może będziesz musiał zrestartować Visual Studio, aby zmiany w kodzie działały w form designer(musiałem, simple rebuild nie działało-lub nie za każdym razem).

Przedstawię moje rozwiązanie tego problemu w przypadku abstrakcyjnej formy bazowej. Załóżmy, że masz klasę BaseForm i chcesz mieć jakieś formularze w oparciu o to, aby było możliwe do zaprojektowania (będzie to Form1). TypeDescriptionProvider przedstawione przez Juana Carlosa Diaza również nie sprawdziły się dla mnie. Oto, jak to działa, łącząc go z rozwiązaniem MiddleClass (przez smelch), ale BEZ #if DEBUG kompilowanie warunkowe i z pewnymi poprawkami:

[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
    }
}

Zwróć uwagę na atrybut na klasie BaseForm. Następnie musisz zadeklarować TypeDescriptionProvideri dwie klasy średnie, ale nie martw się, są one niewidzialne i nieistotne dla programista Form1 . Pierwsza implementuje elementy abstrakcyjne (i sprawia, że klasa bazowa nie jest abstrakcyjna). Drugi jest pusty - jest tylko wymagany do pracy projektanta formularzy VS. Następnie przypisujesz drugą klasę średnią do TypeDescriptionProvider z BaseForm. brak kompilacji warunkowej.

Miałem jeszcze dwa problemy:

  • Problem 1: Po zmianie Form1 w designerze (lub jakimś kodzie) znowu dawał błąd (przy próbie aby otworzyć go ponownie w designerze).
  • Problem 2: kontrolki BaseForm zostały umieszczone nieprawidłowo, gdy rozmiar formularza 1 został zmieniony w designerze, a Formularz został zamknięty i ponownie otwarty w form designer.

Pierwszy problem (możesz go nie mieć, ponieważ jest to coś, co prześladuje mnie w moim projekcie w kilku innych miejscach i zwykle tworzy wyjątek "nie można przekonwertować typu X na typ X"). Rozwiązałem to w TypeDescriptionProvider przez porównując nazwy typów (FullName) zamiast porównywania typów (patrz poniżej).

Drugi problem. Nie wiem, dlaczego kontrolki form bazowych nie są projektowane w klasie Form1, a ich pozycje są tracone po zmianie rozmiaru, ale pracowałem nad tym (nie jest to miłe rozwiązanie - jeśli wiesz lepiej, napisz). Po prostu ręcznie przesuwam przyciski BaseForm (które powinny znajdować się w prawym dolnym rogu) do ich prawidłowych pozycji w metodzie wywoływanej asynchronicznie Z zdarzenia Load BaseForm: BeginInvoke(new Action(CorrectLayout)); Moja klasa bazowa ma tylko przyciski" OK "i" Anuluj", więc sprawa jest prosta.
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
{
}

A tutaj masz nieco zmodyfikowaną wersję 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);
    }
}

I to wszystko!

Nie musisz niczego wyjaśniać przyszłym twórcom formularzy opartych na Twojej bazie i nie muszą robić żadnych sztuczek, aby zaprojektować swoje formularze! Myślę, że jest to najbardziej czyste rozwiązanie, jakie może być (poza repozycjonowaniem sterowania).

Jeszcze jedna wskazówka:

Jeśli dla z jakiegoś powodu projektant nadal odmawia pracy dla Ciebie, zawsze możesz wykonać prostą sztuczkę, zmieniając public class Form1 : BaseForm na public class Form1 : BaseFormMiddle1 (lub BaseFormMiddle2) w pliku kodu, edytując go w vs form designer, a następnie zmieniając go z powrotem. Wolę ten trick niż kompilację warunkową, ponieważ jest mniej prawdopodobne, że zapomni i wyda złą wersję .

 2
Author: P.W.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-07 12:05:15

Projektant formularzy Windows tworzy instancję klasy bazowej formularza / kontrolki i stosuje wynik analizy InitializeComponent. Dlatego możesz zaprojektować formularz stworzony przez kreatora projektu nawet bez budowania projektu. Z powodu tego zachowania nie można również zaprojektować kontrolki pochodzącej z klasy abstrakcyjnej.

Możesz zaimplementować te abstrakcyjne metody i wyrzucić wyjątek, gdy nie jest on uruchomiony w Projektancie. Programista, który korzysta ze sterownika, musi zapewnić implementacja, która nie wywołuje implementacji klasy bazowej. W przeciwnym razie program się zawiesi.

 1
Author: Sheng Jiang 蒋晟,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-23 21:38:15

Możesz po prostu warunkowo skompilować w słowie kluczowym abstract bez interpunkcji oddzielnej klasy:

#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
  }

Działa to pod warunkiem, że BaseForm nie ma żadnych abstrakcyjnych metod(dlatego słowo kluczowe abstract zapobiega tylko uruchomionej instancji klasy).

 0
Author: Peter Gluck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-17 20:31:27