Làm cách nào để tôi có thể yêu cầu trình thiết kế Windows Forms Visual Studio 2008 kết xuất một Biểu mẫu triển khai một lớp cơ sở trừu tượng?


98

Tôi đã gặp sự cố với các Điều khiển kế thừa trong Windows Forms và cần một số lời khuyên về vấn đề đó.

Tôi sử dụng lớp cơ sở cho các mục trong Danh sách (danh sách GUI tự tạo được tạo bằng bảng điều khiển) và một số điều khiển kế thừa dành cho từng loại dữ liệu có thể được thêm vào danh sách.

Không có vấn đề gì với nó, nhưng bây giờ tôi phát hiện ra rằng sẽ đúng khi đặt base-control trở thành một lớp trừu tượng, vì nó có các phương thức, cần được triển khai trong tất cả các điều khiển kế thừa, được gọi từ mã bên trong base-control, nhưng không được và không thể được thực hiện trong lớp cơ sở.

Khi tôi đánh dấu điều khiển cơ sở là trừu tượng, Visual Studio 2008 Designer từ chối tải cửa sổ.

Có cách nào để làm cho Designer làm việc với điều khiển cơ sở được thực hiện trừu tượng không?

Câu trả lời:


97

Tôi BIẾT phải có một cách để làm điều này (và tôi đã tìm ra cách để làm điều này một cách rõ ràng). Giải pháp của Sheng chính xác là những gì tôi đã đưa ra như một giải pháp tạm thời nhưng sau khi một người bạn chỉ ra rằng Formlớp cuối cùng được kế thừa từ một abstractlớp, chúng ta NÊN có thể thực hiện điều này. Nếu họ làm được, chúng ta sẽ làm được.

Chúng tôi đã đi từ mã này đến vấn đề

Form1 : Form

Vấn đề

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

Đây là nơi mà câu hỏi ban đầu xuất hiện. Như đã nói trước đây, một người bạn đã chỉ ra rằng System.Windows.Forms.Formthực thi một lớp cơ sở là trừu tượng. Chúng tôi đã có thể tìm thấy ...

Bằng chứng về một giải pháp tốt hơn

Từ đó, chúng ta biết rằng trình thiết kế có thể hiển thị một lớp đã triển khai một lớp trừu tượng cơ sở, nó chỉ không thể hiển thị một lớp thiết kế đã triển khai ngay một lớp trừu tượng cơ sở. Phải có tối đa 5 ở giữa, nhưng chúng tôi đã thử nghiệm 1 lớp trừu tượng và ban đầu đưa ra giải pháp này.

Giải pháp ban đầu

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

Điều này thực sự hoạt động và trình thiết kế kết xuất nó tốt, vấn đề đã được giải quyết .... ngoại trừ bạn có thêm một cấp độ kế thừa trong ứng dụng sản xuất của mình mà chỉ cần thiết vì sự thiếu sót trong trình thiết kế winforms!

Đây không phải là một giải pháp chắc chắn 100% nhưng nó khá tốt. Về cơ bản, bạn sử dụng #if DEBUGđể đưa ra giải pháp tinh chỉnh.

Giải pháp tinh chỉnh

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

Điều này chỉ sử dụng giải pháp được nêu trong "giải pháp ban đầu", nếu nó đang ở chế độ gỡ lỗi. Ý tưởng là bạn sẽ không bao giờ phát hành chế độ sản xuất thông qua bản dựng gỡ lỗi và bạn sẽ luôn thiết kế ở chế độ gỡ lỗi.

Trình thiết kế sẽ luôn chạy ngược lại mã được tạo trong chế độ hiện tại, vì vậy bạn không thể sử dụng trình thiết kế trong chế độ phát hành. Tuy nhiên, miễn là bạn thiết kế ở chế độ gỡ lỗi và phát hành mã được tích hợp trong chế độ phát hành, bạn có thể sử dụng.

Giải pháp chắc chắn duy nhất sẽ là nếu bạn có thể kiểm tra chế độ thiết kế thông qua chỉ thị tiền xử lý.


3
Biểu mẫu của bạn và lớp cơ sở trừu tượng có hàm tạo no-arg không? Bởi vì đó là tất cả những gì chúng tôi phải thêm vào để nhà thiết kế làm việc để hiển thị một biểu mẫu kế thừa từ một biểu mẫu trừu tượng.
nos

Làm việc rất tốt! Tôi nghĩ rằng tôi sẽ chỉ thực hiện các sửa đổi mà tôi cần đối với các lớp khác nhau thực hiện lớp trừu tượng, sau đó loại bỏ lớp trung gian tạm thời một lần nữa và nếu tôi cần sửa đổi thêm sau này, tôi có thể thêm lại nó. Thực sự, giải pháp đã hoạt động. Cảm ơn!
neminem

1
Giải pháp của bạn hoạt động tuyệt vời. Tôi không thể tin rằng Visual Studio yêu cầu bạn phải vượt qua những vòng lặp như vậy để làm một điều gì đó quá phổ biến.
RB Davidson

1
Nhưng nếu tôi sử dụng middleClass không phải là một lớp trừu tượng, thì bất kỳ ai kế thừa middleClass sẽ không phải triển khai phương thức trừu tượng nữa, điều này phá hủy mục đích sử dụng lớp trừu tượng ngay từ đầu ... Làm thế nào để giải quyết điều này?
Darius

1
@ ti034 Tôi không tìm thấy giải pháp nào khác. Vì vậy, tôi chỉ làm cho các hàm được cho là trừu tượng từ middleClass có một số giá trị mặc định có thể dễ dàng nhắc nhở tôi ghi đè chúng mà không cần trình biên dịch gây lỗi. Ví dụ: nếu phương thức được cho là trừu tượng là trả về tiêu đề của trang, thì tôi sẽ đặt nó trả về một chuỗi "Vui lòng thay đổi tiêu đề".
Darius

74

@smelch, Có một giải pháp tốt hơn mà không cần phải tạo điều khiển ở giữa, ngay cả để gỡ lỗi.

Những gì chúng ta muốn

Đầu tiên, hãy định nghĩa lớp cuối cùng và lớp trừu tượng cơ sở.

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

Bây giờ tất cả những gì chúng ta cần là một nhà cung cấp Mô tả .

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

Cuối cùng, chúng tôi chỉ áp dụng thuộc tính TypeDescriptionProvider cho điều khiển Abastract.

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

Và đó là nó. Không cần kiểm soát trung gian.

Và lớp trình cung cấp có thể được áp dụng cho nhiều cơ sở Trừu tượng mà chúng ta muốn trong cùng một giải pháp.

* CHỈNH SỬA * Ngoài ra, điều sau đây là cần thiết trong app.config

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

Cảm ơn @ user3057544 về đề xuất.



1
Điều này cũng đã làm việc cho tôi rằng tôi đang sử dụng CF 3.5 không cóTypeDescriptionProvider
Adrian Botor

4
Không thể làm cho điều này hoạt động trong VS 2010, mặc dù smelch đã hoạt động. Có ai biết tại sao không?
RobC

5
@RobC Designer hơi khó tính vì một số lý do. Tôi thấy rằng sau khi thực hiện bản sửa lỗi này, tôi phải làm sạch giải pháp, đóng & khởi chạy lại VS2010 và xây dựng lại; sau đó nó sẽ cho phép tôi thiết kế lớp con.
Hiền nhân hiển nhiên,

3
Cần lưu ý rằng vì bản sửa lỗi này thay thế một thể hiện của lớp cơ sở cho lớp trừu tượng, các phần tử trực quan được thêm vào trong Trình thiết kế cho lớp trừu tượng sẽ không khả dụng khi thiết kế các lớp con.
Hiền nhân hiển nhiên,

1
Điều này đã hiệu quả với tôi, nhưng lần đầu tiên tôi phải khởi động lại VS 2013 sau khi xây dựng dự án. @ObliviousSage - Cảm ơn bạn đã chú ý; trong trường hợp hiện tại của tôi ít nhất đây không phải là một vấn đề nhưng vẫn là một vấn đề tốt để đề phòng.
InteXX

10

@Smelch, cảm ơn vì câu trả lời hữu ích, vì gần đây tôi đang gặp phải vấn đề tương tự.

Sau đây là một thay đổi nhỏ đối với bài đăng của bạn để ngăn cảnh báo biên dịch (bằng cách đặt lớp cơ sở trong #if DEBUGchỉ thị tiền xử lý):

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

5

Tôi gặp sự cố tương tự nhưng đã tìm ra cách để cấu trúc lại mọi thứ để sử dụng giao diện thay cho lớp cơ sở trừu tượng:

interface Base {....}

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

Điều này có thể không áp dụng cho mọi tình huống, nhưng khi có thể, nó dẫn đến một giải pháp sạch hơn so với biên dịch có điều kiện.


1
Bạn có thể cung cấp một mẫu mã hoàn chỉnh hơn không? Tôi đang cố gắng hiểu thiết kế của bạn tốt hơn và tôi cũng sẽ dịch nó sang VB. Cảm ơn.
InteXX

Tôi biết điều này đã cũ nhưng tôi thấy đây là giải pháp ít hack nhất. Vì tôi vẫn muốn giao diện của mình gắn liền với UserControl, tôi đã thêm một thuộc UserControltính vào giao diện và tham chiếu rằng bất cứ khi nào tôi cần truy cập trực tiếp vào nó. Trong triển khai giao diện của mình, tôi mở rộng UserControl và đặt thuộc UserControltính thànhthis
chanban

3

Tôi đang sử dụng giải pháp trong câu trả lời này cho một câu hỏi khác, liên kết đến bài viết này . Bài viết khuyến nghị sử dụng một TypeDescriptionProvidertriển khai tùy chỉnh và cụ thể của lớp trừu tượng. Nhà thiết kế sẽ hỏi nhà cung cấp tùy chỉnh loại nào để sử dụng và mã của bạn có thể trả về lớp cụ thể để nhà thiết kế hài lòng trong khi bạn có toàn quyền kiểm soát cách lớp trừu tượng xuất hiện như một lớp cụ thể.

Cập nhật: Tôi đã bao gồm một mẫu mã tài liệu trong câu trả lời của tôi cho câu hỏi khác đó. Mã ở đó hoạt động, nhưng đôi khi tôi phải trải qua một chu trình xây dựng / sạch như đã lưu ý trong câu trả lời của tôi để nó hoạt động.


3

Tôi có một số lời khuyên cho những người nói rằng công cụ TypeDescriptionProvidercủa Juan Carlos Diaz không hoạt động và không thích biên dịch có điều kiện:

Trước hết, bạn có thể phải khởi động lại Visual Studio để các thay đổi trong mã của bạn hoạt động trong trình thiết kế biểu mẫu (tôi phải làm vậy, xây dựng lại đơn giản không hoạt động - hoặc không phải lúc nào cũng vậy).

Tôi sẽ trình bày giải pháp của tôi cho vấn đề này cho trường hợp của Biểu mẫu cơ sở trừu tượng. Giả sử bạn có một BaseFormlớp học và bạn muốn bất kỳ biểu mẫu nào dựa trên nó có thể được chỉ định (điều này sẽ là Form1). Những TypeDescriptionProvidergì được trình bày bởi Juan Carlos Diaz cũng không phù hợp với tôi. Đây là cách tôi làm cho nó hoạt động, bằng cách kết hợp nó với giải pháp MiddleClass (bởi smelch), nhưng không có#if DEBUG biên dịch có điều kiện và với một số sửa đổi:

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

Lưu ý thuộc tính trên lớp BaseForm. Sau đó, bạn chỉ cần khai báo TypeDescriptionProviderhai lớp giữa , nhưng đừng lo lắng, chúng vô hình và không liên quan đối với nhà phát triển của Form1 . Cái đầu tiên thực hiện các thành viên trừu tượng (và làm cho lớp cơ sở không trừu tượng). Cái thứ hai trống - nó chỉ cần thiết để trình thiết kế biểu mẫu VS hoạt động. Sau đó bạn gán thứ hai tầng lớp trung lưu cho TypeDescriptionProvidercủa BaseForm. Không có điều kiện biên dịch.

Tôi gặp phải hai vấn đề nữa:

  • Sự cố 1: Sau khi thay đổi Form1 trong trình thiết kế (hoặc một số mã), nó lại xuất hiện lỗi (khi cố gắng mở lại trong trình thiết kế).
  • Vấn đề 2: Các điều khiển của BaseForm được đặt không chính xác khi kích thước của Form1 được thay đổi trong trình thiết kế và biểu mẫu đã bị đóng và mở lại trong trình thiết kế biểu mẫu.

Vấn đề đầu tiên (bạn có thể không mắc phải vì đó là thứ ám ảnh tôi trong dự án của tôi ở một vài nơi khác và thường tạo ra ngoại lệ "Không thể chuyển loại X thành loại X"). Tôi đã giải quyết nó TypeDescriptionProviderbằng cách so sánh tên kiểu (FullName) thay vì so sánh các kiểu (xem bên dưới).

Vấn đề thứ hai. Tôi thực sự không biết tại sao các điều khiển của biểu mẫu cơ sở không thể chỉ định trong lớp Form1 và vị trí của chúng bị mất sau khi thay đổi kích thước, nhưng tôi đã giải quyết vấn đề đó (không phải là một giải pháp hay - nếu bạn biết bất kỳ điều gì tốt hơn, vui lòng viết). Tôi chỉ cần di chuyển các nút của BaseForm theo cách thủ công (phải ở góc dưới bên phải) đến vị trí chính xác của chúng trong một phương thức được gọi không đồng bộ từ sự kiện Tải của BaseForm: BeginInvoke(new Action(CorrectLayout));Lớp cơ sở của tôi chỉ có các nút "OK" và "Hủy", vì vậy trường hợp là đơn giản.

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

Và ở đây bạn có phiên bản sửa đổi một chút của 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);
    }
}

Và đó là nó!

Bạn không cần phải giải thích bất cứ điều gì cho các nhà phát triển tương lai của biểu mẫu dựa trên BaseForm của bạn và họ không phải thực hiện bất kỳ thủ thuật nào để thiết kế biểu mẫu của mình! Tôi nghĩ đó là giải pháp sạch sẽ nhất có thể (ngoại trừ việc định vị lại các nút điều khiển).

Một mẹo nữa:

Nếu vì lý do nào đó mà trình thiết kế vẫn từ chối làm việc cho bạn, bạn luôn có thể thực hiện thủ thuật đơn giản là thay đổi public class Form1 : BaseFormthành public class Form1 : BaseFormMiddle1(hoặc BaseFormMiddle2) trong tệp mã, chỉnh sửa nó trong trình thiết kế biểu mẫu VS và sau đó thay đổi lại. Tôi thích thủ thuật này hơn là biên dịch có điều kiện vì nó ít có khả năng quên và phát hành phiên bản sai hơn .


1
Điều này đã giải quyết được vấn đề tôi gặp phải với giải pháp của Juan trong VS 2013; khi khởi động lại VS, các điều khiển tải liên tục ngay bây giờ.
Luke Merrett

3

Tôi có một mẹo cho giải pháp Juan Carlos Diaz. Nó hoạt động tốt cho tôi, nhưng có một số vấn đề với nó. Khi tôi bắt đầu VS và nhập thiết kế mọi thứ hoạt động tốt. Nhưng sau khi chạy giải pháp, sau đó dừng lại và thoát khỏi nó và sau đó cố gắng nhập trình thiết kế, ngoại lệ xuất hiện lặp đi lặp lại cho đến khi khởi động lại VS. Nhưng tôi đã tìm thấy giải pháp cho nó - mọi thứ cần làm là thêm bên dưới vào app.config của bạn

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

2

Vì lớp trừu tượng public abstract class BaseForm: Formtạo ra một lỗi và tránh việc sử dụng trình thiết kế, tôi đã sử dụng các thành viên ảo. Về cơ bản, thay vì khai báo các phương thức trừu tượng, tôi đã khai báo các phương thức ảo với phần thân tối thiểu nhất có thể. Đây là những gì tôi đã làm:

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được cho là một lớp trừu tượng với thành viên trừu tượng displayFields, tôi "giả mạo" hành vi này với các thành viên ảo để tránh sự trừu tượng hóa. Nhà thiết kế không phàn nàn nữa và mọi thứ đều ổn đối với tôi.

Đó là một cách giải quyết dễ đọc hơn, nhưng vì nó không trừu tượng, tôi phải đảm bảo rằng tất cả các lớp con của DataFormchúng đều có sự triển khai của chúng displayFields. Vì vậy, hãy cẩn thận khi sử dụng kỹ thuật này.


Đây là những gì tôi đã đi cùng. Tôi chỉ ném NotImplementedException trong lớp cơ sở để làm cho lỗi rõ ràng nếu nó bị quên.
Shaun Rowan

1

Windows Forms Designer đang tạo một thể hiện của lớp cơ sở của biểu mẫu / điều khiển của bạn và áp dụng kết quả phân tích cú pháp của InitializeComponent. Đó là lý do tại sao bạn có thể thiết kế biểu mẫu được tạo bởi trình hướng dẫn dự án mà không cần xây dựng dự án. Do hành vi này, bạn cũng không thể thiết kế một điều khiển bắt nguồn từ một lớp trừu tượng.

Bạn có thể triển khai các phương thức trừu tượng đó và ném một ngoại lệ khi nó không chạy trong trình thiết kế. Lập trình viên xuất phát từ điều khiển phải cung cấp một triển khai không gọi việc triển khai lớp cơ sở của bạn. Nếu không chương trình sẽ bị sập.


đáng tiếc, nhưng đó là cách nó được thực hiện được nêu ra. Hy vọng về một cách chính xác để làm điều này.
Oliver Friedrich

Có một cách tốt hơn, xem câu trả lời Smelch của
Allen Rice

-1

Bạn chỉ có thể biên dịch có điều kiện trong abstracttừ khóa mà không cần xen kẽ một lớp riêng biệt:

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

Điều này hoạt động với điều kiện BaseFormkhông có bất kỳ phương thức trừu tượng nào ( abstractdo đó từ khóa chỉ ngăn chặn việc khởi tạo thời gian chạy của lớp).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.