[c#] Visual Studio 2008 Windows Forms 디자이너가 추상 기본 클래스를 구현하는 Form을 렌더링하도록하려면 어떻게해야합니까?

Windows Forms의 상속 된 컨트롤에 문제가 발생했으며 이에 대한 조언이 필요합니다.

목록 (패널로 만든 자체 제작 GUI 목록)의 항목에 대한 기본 클래스와 목록에 추가 할 수있는 각 데이터 유형에 대한 일부 상속 된 컨트롤을 사용합니다.

아무런 문제가 없었지만 이제는 기본 컨트롤을 추상 클래스로 만드는 것이 옳다는 것을 알게되었습니다. 메서드가 있기 때문에 상속 된 모든 컨트롤에서 구현되어야하며 내부 코드에서 호출됩니다. 기본 제어이지만 기본 클래스에서 구현할 수 없으며 구현할 수 없습니다.

기본 컨트롤을 추상으로 표시하면 Visual Studio 2008 디자이너가 창로드를 거부합니다.

디자이너가 기본 컨트롤을 추상적으로 사용하도록하는 방법이 있습니까?



답변

나는 이것을 할 방법이 있어야한다는 것을 알고 있었다 (그리고 이것을 깨끗하게 할 방법을 찾았다). Sheng의 솔루션은 제가 임시 해결 방법으로 생각 해낸 것과 정확히 일치하지만 친구가 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
... 

이것이하는 일은 디버그 모드에있는 경우 “초기 솔루션”에 설명 된 솔루션 만 사용하는 것입니다. 아이디어는 디버그 빌드를 통해 프로덕션 모드를 릴리스하지 않으며 항상 디버그 모드에서 디자인한다는 것입니다.

디자이너는 항상 현재 모드에서 빌드 된 코드에 대해 실행되므로 릴리스 모드에서 디자이너를 사용할 수 없습니다. 그러나 디버그 모드에서 디자인하고 릴리스 모드에서 빌드 된 코드를 릴리스하는 한 계속 진행할 수 있습니다.

유일한 확실한 해결책은 전 처리기 지시문을 통해 디자인 모드를 테스트 할 수있는 경우입니다.


답변

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

마지막으로 Abastract 컨트롤에 TypeDescriptionProvider 특성을 적용합니다.

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

그리고 그게 다야. 중간 제어가 필요하지 않습니다.

공급자 클래스는 동일한 솔루션에서 원하는만큼의 Abstract베이스에 적용 할 수 있습니다.

* 편집 *
또한 app.config에 다음이 필요합니다.

<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

업데이트 : 다른 질문에 대한 답변에 문서화 된 코드 샘플 을 포함 했습니다 . 거기의 코드는 작동하지만 때로는 작동하도록 내 대답에 명시된대로 정리 / 빌드주기를 거쳐야합니다.


답변

TypeDescriptionProviderJuan Carlos Diaz가 작동하지 않고 조건부 컴파일도 마음에 들지 않는다고 말하는 사람들을위한 몇 가지 팁 이 있습니다.

우선, 코드 변경 사항이 폼 디자이너에서 작동하도록 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 양식 디자이너가 작동하는 데 필요합니다. 그럼 당신은 지정 둘째 받는 중산층 TypeDescriptionProviderBaseForm. 조건부 컴파일이 없습니다.

두 가지 문제가 더 있습니다.

  • 문제 1 : 디자이너 (또는 일부 코드)에서 Form1을 변경 한 후 다시 오류가 발생했습니다 (디자이너에서 다시 열려고 할 때).
  • 문제 2 : 디자이너에서 Form1의 크기가 변경되고 폼이 닫혔다가 폼 디자이너에서 다시 열릴 때 BaseForm의 컨트롤이 잘못 배치되었습니다.

첫 번째 문제 (내 프로젝트에서 다른 곳에서 저를 괴롭 히고 일반적으로 “유형 X를 유형 X로 변환 할 수 없음”예외를 생성하기 때문에 문제가 없을 수 있습니다). 유형 을 비교하는 대신 유형 이름 (FullName) TypeDescriptionProvider비교하여 해결했습니다 (아래 참조).

두 번째 문제입니다. 기본 폼의 컨트롤이 Form1 클래스에서 디자인 할 수없고 크기 조정 후 위치가 손실되는 이유는 모르겠지만 해결했습니다 (좋은 해결책이 아닙니다. 더 잘 알고 있다면 작성하십시오). BaseForm의 Load 이벤트에서 비동기 적으로 호출 된 메서드에서 BaseForm의 버튼 (오른쪽 하단 모서리에 있어야 함)을 올바른 위치로 수동으로 이동합니다. BeginInvoke(new Action(CorrectLayout));기본 클래스에는 “OK”및 “Cancel”버튼 만 있습니다. 케이스는 간단합니다.

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 : BaseFormto public class Form1 : BaseFormMiddle1(또는 BaseFormMiddle2)를 변경하고 VS 양식 디자이너에서 편집 한 다음 다시 변경 하는 간단한 트릭을 수행 할 수 있습니다 . 조건부 컴파일보다이 트릭을 선호합니다. 왜냐하면 잘못된 버전을 잊고 릴리스 할 가능성이 적기 때문 입니다.


답변

Juan Carlos Diaz 솔루션에 대한 팁이 있습니다. 저에게는 잘 작동하지만 문제가있었습니다. VS를 시작하고 디자이너에 들어가면 모든 것이 잘 작동합니다. 그러나 솔루션을 실행 한 후 중지하고 종료 한 다음 디자이너를 입력하려고 시도하면 VS를 다시 시작할 때까지 예외가 계속해서 나타납니다. 하지만 해결책을 찾았습니다.해야 할 일은 아래에 app.config를 추가하는 것입니다.

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