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


98

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

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

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

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

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

답변:


97

나는 이것을 할 방법이 있어야한다는 것을 알고 있었다 (그리고 이것을 깨끗하게 할 방법을 찾았다). 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
... 

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

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

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


3
양식과 추상 기본 클래스에 인수가없는 생성자가 있습니까? 그게 디자이너가 추상 양식에서 상속 된 양식을 보여주기 위해 추가해야하는 전부이기 때문입니다.
nos

잘 했어! 추상 클래스를 구현하는 다양한 클래스에 필요한 수정을 한 다음 임시 중간 클래스를 다시 제거하고 나중에 더 수정해야 할 경우 다시 추가 할 수 있다고 생각합니다. 해결 방법은 실제로 작동했습니다. 감사!
neminem

1
귀하의 솔루션은 훌륭하게 작동합니다. 나는 Visual Studio가 그렇게 일반적인 일을하기 위해 그런 후프를 뛰어 넘어야한다고 믿을 수 없다.
RB Davidson

1
그러나 추상 클래스가 아닌 middleClass를 사용하면 middleClass를 상속받은 사람은 더 이상 추상 메서드를 구현할 필요가 없습니다. 이것은 처음에 추상 클래스를 사용하는 목적을 무너 뜨립니다. 어떻게 해결할까요?
Darius

1
@ ti034 해결 방법을 찾을 수 없습니다. 그래서 나는 단지 middleClass의 추상적 인 함수가 컴파일러가 오류를 던지지 않고 그것들을 재정의하도록 쉽게 상기시킬 수있는 몇 가지 기본값을 갖도록 만듭니다. 예를 들어, 추상적 인 방법이 페이지의 제목을 반환하는 것이라면 "제목을 변경하십시오"라는 문자열을 반환하도록 할 것입니다.
Darius

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

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

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

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

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

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

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

제안에 대해 @ user3057544에게 감사드립니다.



1
이것은 또한 내가 CF 3.5을 사용하고 것이 더 있다는 것을 나를 위해 작동하지 않았다TypeDescriptionProvider
아드리안 Botor

4
smelch가 작동했지만 VS 2010에서는 작동하지 못했습니다. 왜 그럴까요?
RobC 2014

5
@RobC Designer는 어떤 이유로 든 언짢습니다. 이 수정 사항을 구현 한 후 솔루션을 정리하고 VS2010을 닫았다가 다시 시작한 다음 다시 빌드해야한다는 것을 알았습니다. 그러면 하위 클래스를 디자인 할 수 있습니다.
Oblivious Sage

3
이 수정은 추상 클래스에 대한 기본 클래스의 인스턴스를 대체하기 때문에 추상 클래스에 대한 디자이너에서 추가 된 시각적 요소 는 하위 클래스를 디자인 할 때 사용할 수 없습니다 .
Oblivious Sage

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

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


1
이것은 VS 2013에서 Juan의 솔루션과 관련된 문제를 해결했습니다. VS를 다시 시작할 때 컨트롤이 이제 일관되게로드됩니다.
Luke Merrett 2015 년

3

Juan Carlos Diaz 솔루션에 대한 팁이 있습니다. 저에게는 잘 작동하지만 문제가있었습니다. VS를 시작하고 디자이너에 들어가면 모든 것이 잘 작동합니다. 그러나 솔루션을 실행 한 후 중지하고 종료 한 다음 디자이너를 입력하려고 시도하면 VS를 다시 시작할 때까지 예외가 계속해서 나타납니다. 하지만 해결책을 찾았습니다.해야 할 일은 아래에 app.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를 피하기 위해 가상 멤버로이 동작을 "위조"합니다. 디자이너는 더 이상 불평하지 않으며 모든 것이 잘 작동합니다.

더 읽기 쉬운 해결 방법이지만 추상이 아니기 때문에의 모든 자식 클래스 DataFormdisplayFields. 따라서이 기술을 사용할 때주의하십시오.


이것이 제가 함께했던 것입니다. 잊혀진 경우 오류를 명확히하기 위해 기본 클래스에 NotImplementedException을 던졌습니다.
Shaun Rowan

1

Windows Forms 디자이너는 양식 / 컨트롤의 기본 클래스 인스턴스를 만들고 InitializeComponent. 그래서 프로젝트를 빌드하지 않고도 프로젝트 마법사로 만든 양식을 디자인 할 수 있습니다. 이 동작으로 인해 추상 클래스에서 파생 된 컨트롤도 디자인 할 수 없습니다.

이러한 추상 메서드를 구현하고 디자이너에서 실행되지 않을 때 예외를 throw 할 수 있습니다. 컨트롤에서 파생 된 프로그래머는 기본 클래스 구현을 호출하지 않는 구현을 제공해야합니다. 그렇지 않으면 프로그램이 충돌합니다.


안타깝지만 그것이 아직 끝난 방법입니다. 이 작업을 수행하는 올바른 방법을 원했습니다.
Oliver Friedrich

더 좋은 방법이 있습니다, Smelch의 답변을 참조
알렌 쌀

-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따라서 키워드는 클래스의 런타임 인스턴스화 만 방지 함).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.