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ędzie Form1
). 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:
[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 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ą do TypeDescriptionProvider
z BaseForm
. Brak kompilacji warunkowej.
Miałem jeszcze dwa problemy:
- Problem 1: Po zmianie Form1 w projektancie (lub w jakimś kodzie) ponownie dawał błąd (podczas próby ponownego otwarcia go w projektancie).
- Problem 2: Kontrolki BaseForm zostały umieszczone nieprawidłowo, gdy rozmiar Form1 został zmieniony w projektancie, a formularz został zamknięty i ponownie otwarty w projektancie formularzy.
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.
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 oto nieco zmodyfikowana wersja 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 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
(lub BaseFormMiddle2
) 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ę .