WIEDZIAŁEM, że musi być 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 znajomy wskazał, że Form
klasa ostatecznie odziedziczyła po abstract
klasie, POWINIENEMY być w stanie to zrobić. Jeśli mogą to zrobić, możemy to zrobić.
Przeszliśmy od tego kodu do problemu
Form1 : Form
Problem
public class Form1 : BaseForm
...
public abstract class BaseForm : Form
W tym miejscu pojawiło się pierwsze pytanie. Jak wspomniano wcześniej, znajomy wskazał, że System.Windows.Forms.Form
implementuje abstrakcyjną klasę bazową. Udało nam się znaleźć ...
Dowód lepszego rozwiązania
Dzięki temu wiedzieliśmy, że projektant mógł pokazać klasę, która zaimplementowała podstawową klasę abstrakcyjną, po prostu nie mógł pokazać klasy projektanta, która natychmiast zaimplementowała podstawową klasę abstrakcyjną. Musiało być maksymalnie 5 między nimi, ale przetestowaliśmy 1 warstwę abstrakcji i początkowo wymyśliliśmy to rozwiązanie.
Wstępne rozwiązanie
public class Form1 : MiddleClass
...
public class MiddleClass : BaseForm
...
public abstract class BaseForm : Form
...
To faktycznie działa i projektant czyni to dobrze, problem rozwiązany… z wyjątkiem tego, że masz dodatkowy poziom dziedziczenia w swojej aplikacji produkcyjnej, który był konieczny tylko z powodu nieadekwatności w projektancie winforms!
To nie jest w 100% pewne rozwiązanie, ale jest całkiem dobre. Zasadniczo używasz #if DEBUG
do wymyślania wyrafinowanego rozwiązania.
Dopracowane rozwiązanie
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
...
To, co to robi, to użycie rozwiązania opisanego w „rozwiązaniu początkowym”, tylko jeśli jest w trybie debugowania. Chodzi o to, że nigdy nie wydasz trybu produkcyjnego poprzez kompilację do debugowania i że zawsze będziesz projektować w trybie debugowania.
Projektant zawsze będzie działał na podstawie kodu zbudowanego w bieżącym trybie, więc nie można używać projektanta w trybie wydania. Jednak jeśli projektujesz w trybie debugowania i publikujesz kod zbudowany w trybie wydania, możesz działać.
Jedynym pewnym rozwiązaniem byłoby przetestowanie trybu projektowania za pomocą dyrektywy preprocesora.
@smelch, jest lepsze rozwiązanie, bez konieczności tworzenia środkowej kontrolki, nawet do debugowania.
Czego chcemy
Najpierw zdefiniujmy ostatnią klasę i podstawową klasę abstrakcyjną.
Teraz potrzebujemy tylko dostawcy opisu .
Na koniec po prostu stosujemy atrybut TypeDescriptionProvider do kontrolki Abastract.
I to wszystko. Nie jest wymagana środkowa kontrola.
Klasa dostawcy może być zastosowana do dowolnej liczby baz abstrakcyjnych w tym samym rozwiązaniu.
* EDYCJA * W pliku app.config potrzebne są również następujące elementy
Dzięki @ user3057544 za sugestię.
źródło
TypeDescriptionProvider
@Smelch, dziękuję za pomocną odpowiedź, ponieważ ostatnio miałem ten sam problem.
Poniżej znajduje się niewielka zmiana w Twoim poście, aby zapobiec ostrzeżeniom dotyczącym kompilacji (poprzez umieszczenie klasy bazowej w
#if DEBUG
dyrektywie preprocesora):źródło
Miałem podobny problem, ale znalazłem sposób na zreformowanie rzeczy, aby użyć interfejsu zamiast abstrakcyjnej klasy bazowej:
Może to nie mieć zastosowania w każdej sytuacji, ale jeśli to możliwe, skutkuje czystszym rozwiązaniem niż kompilacja warunkowa.
źródło
UserControl
właściwość do interfejsu i odwoływałem się do niej, gdy potrzebowałem bezpośredniego dostępu do niego. W moich implementacjach interfejsu rozszerzam UserControl i ustawiamUserControl
właściwość nathis
Korzystam z rozwiązania w tej odpowiedzi na inne pytanie, które łączy ten artykuł . Artykuł zaleca użycie niestandardowej
TypeDescriptionProvider
i konkretnej implementacji klasy abstrakcyjnej. Projektant zapyta niestandardowego dostawcę, których typów użyć, a kod może zwrócić konkretną klasę, aby projektant był zadowolony, gdy masz pełną kontrolę nad wyglądem klasy abstrakcyjnej jako konkretnej klasy.Aktualizacja: dołączyłem udokumentowany przykład kodu do mojej odpowiedzi na to drugie pytanie. Kod tam działa, ale czasami muszę przejść przez cykl czyszczenia / kompilacji, jak wskazałem w mojej odpowiedzi, aby go uruchomić.
źródło
Mam kilka wskazówek dla osób, które twierdzą, że
TypeDescriptionProvider
Juan Carlos Diaz nie działa i nie podoba im się również kompilacja warunkowa:Przede wszystkim może być konieczne ponowne uruchomienie programu Visual Studio, aby zmiany w kodzie działały w projektancie formularzy (musiałem, prosta przebudowa nie działała - lub nie za każdym razem).
Przedstawię moje rozwiązanie tego problemu dla przypadku abstrakcyjnej formy bazowej. Powiedzmy, że masz
BaseForm
klasę i chcesz, aby wszelkie oparte na niej formularze były możliwe do zaprojektowania (tak będzieForm1
). To,TypeDescriptionProvider
co zaprezentował Juan Carlos Diaz, również mi nie wyszło. Oto jak to działało, łącząc go z rozwiązaniem MiddleClass (przez smelch), ale bez#if DEBUG
kompilacji warunkowej iz pewnymi poprawkami:Zwróć uwagę na atrybut w klasie BaseForm. Wtedy po prostu trzeba zadeklarować
TypeDescriptionProvider
i dwie klasy średniej , ale nie martw się, są one niewidoczne i nieistotne dla dewelopera Form1 . Pierwsza implementuje abstrakcyjne elementy członkowskie (i sprawia, że klasa bazowa nie jest abstrakcyjna). Drugi jest pusty - jest po prostu wymagany do działania projektanta formularzy VS. Następnie należy przypisać drugą klasę średnią doTypeDescriptionProvider
zBaseForm
. Brak kompilacji warunkowej.Miałem jeszcze dwa problemy:
Pierwszy problem (możesz go nie mieć, ponieważ jest to coś, co prześladuje mnie w moim projekcie w kilku innych miejscach i zwykle powoduje wyjątek „Nie można przekonwertować typu X na typ X”). Rozwiązałem to w programie
TypeDescriptionProvider
, porównując nazwy typów (FullName) zamiast porównywać typy (patrz poniżej).Drugi problem. Naprawdę nie wiem, dlaczego kontrolki formularza podstawowego nie są projektowane w klasie Form1 i ich pozycje są tracone po zmianie rozmiaru, ale obejrzałem to (nie jest to fajne rozwiązanie - jeśli wiesz coś lepszego, napisz). Po prostu ręcznie przenoszę przyciski BaseForm (które powinny znajdować się w prawym dolnym rogu) na ich właściwe pozycje w metodzie wywoływanej asynchronicznie ze zdarzenia Load w BaseForm:
BeginInvoke(new Action(CorrectLayout));
Moja klasa bazowa ma tylko przyciski „OK” i „Anuluj”, więc sprawa jest prosta.A oto nieco zmodyfikowana wersja
TypeDescriptionProvider
:I to wszystko!
Nie musisz niczego wyjaśniać przyszłym twórcom formularzy opartych na Twoim BaseForm, a oni nie muszą wykonywać żadnych sztuczek, aby zaprojektować swoje formularze! Myślę, że to najczystsze rozwiązanie, jakie może być (poza repozycjonowaniem elementów sterujących).
Jeszcze jedna wskazówka:
Jeśli z jakiegoś powodu projektant nadal odmawia pracy dla Ciebie, zawsze można zrobić prosty trik zmieniając
public class Form1 : BaseForm
siępublic class Form1 : BaseFormMiddle1
(lubBaseFormMiddle2
) w pliku kodu, edytując go w projektancie formularza VS, a następnie zmieniając go z powrotem. Wolę tę sztuczkę niż kompilację warunkową, ponieważ jest mniej prawdopodobne, że zapomnę i wydam niewłaściwą wersję .źródło
Mam wskazówkę dotyczącą rozwiązania Juan Carlos Diaz. U mnie działa świetnie, ale był z tym jakiś problem. Kiedy uruchamiam VS i wchodzę w projektanta, wszystko działa dobrze. Ale po uruchomieniu rozwiązania, następnie zatrzymaj i wyjdź z niego, a następnie spróbuj wejść do projektanta, wyjątek pojawia się wielokrotnie, aż do ponownego uruchomienia VS. Ale znalazłem na to rozwiązanie - wszystko, co musisz zrobić, to dodać poniżej do pliku app.config
źródło
Ponieważ klasa abstrakcyjna
public abstract class BaseForm: Form
daje błąd i unika korzystania z projektanta, przyszedłem z wykorzystaniem wirtualnych członków. Zasadniczo zamiast deklarować metody abstrakcyjne, zadeklarowałem metody wirtualne z możliwie najmniejszą ilością treści. Oto, co zrobiłem:Ponieważ
DataForm
miała to być klasa abstrakcyjna z elementem abstrakcyjnymdisplayFields
, „udaje” to zachowanie z członkami wirtualnymi, aby uniknąć abstrakcji. Projektant nie narzeka już i u mnie wszystko działa dobrze.Jest to bardziej czytelne obejście, ale ponieważ nie jest abstrakcyjne, muszę się upewnić, że wszystkie klasy potomne
DataForm
mają implementacjędisplayFields
. Dlatego zachowaj ostrożność podczas korzystania z tej techniki.źródło
Projektant formularzy systemu Windows tworzy wystąpienie klasy bazowej formularza / kontrolki i stosuje wynik analizy
InitializeComponent
. Dlatego możesz zaprojektować formularz utworzony przez kreatora projektu, nawet bez budowania projektu. Z powodu tego zachowania nie można również zaprojektować formantu pochodzącego z klasy abstrakcyjnej.Możesz zaimplementować te abstrakcyjne metody i zgłosić wyjątek, gdy nie jest on uruchomiony w projektancie. Programista, który wywodzi się z formantu, musi zapewnić implementację, która nie wywołuje implementacji klasy bazowej. W przeciwnym razie program ulegnie awarii.
źródło
Możesz po prostu warunkowo wkompilować
abstract
słowo kluczowe bez wstawiania oddzielnej klasy:Działa to pod warunkiem, że
BaseForm
nie ma żadnych metod abstrakcyjnych (abstract
słowo kluczowe zapobiega zatem tylko tworzeniu wystąpienia klasy w czasie wykonywania).źródło