10 Ways to Create Maintainable and Testable Windows Forms Applications
Most Windows Forms applications I come across have non-existent or extremely low unit test coverage. And they are also typically very hard to maintain, with hundreds if not thousands of lines of code in the code behind for the various Form classes in the project. But it doesn’t have to be this way. Just because Windows Forms is a “legacy” technology doesn’t mean you are doomed to create an unmaintainable mess. Here’s ten tips for creating maintainable and testable Windows Forms applications. I’ll be covering many of these in my forthcoming Pluralsight course on Windows Forms best practices.
1. Segregate your user interface with User Controls
First, avoid putting too many controls onto a single form. Usually the main form of your application can be broken up into logical areas (which we could call “views”). You will make life much easier for yourself if you put the controls for each of these areas into their own container, and in Windows Forms, the easiest way to do this is with a User Control. So if you have an explorer style application, with a tree view on the left and details view on the right, then put the TreeView into its own UserControl, and create a UserControl for each of the possible right-hand views. Likewise if you have a tab control, create a separate UserControl for each page in the tab control.
Doing this not only keeps your classes from getting unmanageably large, but it also makes tasks like setting up resizing and tab order much more straightforward. It also allows you to easily disable whole sections of your user interface in one go where necessary. You’ll also find when you break up your user interface into smaller UserControls containing logically grouped controls, that it becomes much easier to redesign the UI layout of your application.
2. Keep non UI code out of code behind
Invariably in Windows Forms applications you’ll find code accessing the network, database or file system in the code behind of a form. This is a serious violation of the “Single Responsibility Principle”. The focus of your Form or UserControl class should simply be the user interface. So when you detect non UI related code exists in your code behind, refactor it out into a class with a single responsibility. So you might create a PreferencesManager class for example, or a class that is responsible for calls to a particular web service. These classes can then be injected as dependencies into your UI components (although this is just the first step – we can take this idea further as we’ll see shortly).
3. Create passive views with interfaces
One particularly helpful technique is to make each of the forms and user controls you create implement a view interface. This interface should contain properties that allow the state and content of the controls in the view to be set and retrieved. It may also include events to report back user interactions such as clicking on a button or moving a slider. The goal is that the implementation of these view interfaces is completely passive. Ideally there should be no conditional logic whatsoever in the code behind of your Forms and UserControls.
Here’s an example of a view interface for a new user entry view. The implementation of this view should be trivial. Any business logic doesn’t belong in the code behind (and we’ll discuss where it does belong next).
interface INewUserView
{
string FirstName { get; set; }
string LastName { get; set; }
event EventHandler SaveClicked;
}
By ensuring that your view implementations are as simple as possible, you will maximise your chances of being able to migrate to an alternative UI framework such as WPF, since the only thing you will need to do is recreate the views in the new technology. All your other code can be reused.
4. Use presenters to control the views
So if you have made all your views passive and implement interfaces, you need something that will implement the business logic of your application and control the views. And we can call these “presenter” classes. This is the pattern known as “Model View Presenter” or MVP.
In Model View Presenter your views are completely passive, and the presenter instructs the view what data to display. The view is also allowed to communicate back to the presenter. In my example above it does so by raising an event, but often with this pattern your view is allowed to make direct calls into the presenter.
What is absolutely not allowed is for the view to start directly manipulating the model (which includes your business entities, database layer etc). If you follow the MVP pattern, all the business logic in your application can be easily tested because it resides inside presenter or other non-UI classes.
5. Create services for error reporting
Often your presenter classes will need to display error messages. But don’t just put a MessageBox.Show into a non-UI class. You’ll make that method impossible to unit test. Instead create a service (say IErrorDisplayService) that your presenter can call into whenever it needs to report a problem. This keeps your presenters unit testable, and also provides flexibility to change the way errors are presented to the user in the future.
6. Use the Command pattern
If your application contains a toolbar with a large number of buttons for the user to click, the Command pattern may be a very good fit. The command pattern dictates that you create a class for each command. This has the great benefit of segregating your code into small classes each with a single responsibility. It also allows you to centralise everything to do with a particular command. Should the command be enabled? Should it be visible? What is its tooltip and shortcut key? Does it require a specific privilege or license to execute? How should we handle exceptions thrown when the command runs?
The command pattern allows you to standardise how you deal with each of these concerns that are common to all the commands in your application. Your command object will have an Execute method that actually contains the code to perform the required behaviour for that command. In many cases this will involve calling into other objects and business services, so you will need to inject those as dependencies into your command object. Your command objects themselves should be possible (and straightfoward) to unit test.
7. Use an IoC container to manage dependencies
If you are using Presenter classes and Command classes, then you will probably find that the number of classes they depend on grows over time. This is where an Inversion of Control container such as Unity or StructureMap can really help you out. They allow you to easily construct your views and presenters no matter how many levels of dependencies they have.
8. Use the Event Aggregator pattern
Another design pattern that can be really useful in a Windows Forms application is the event aggregator pattern (sometimes also called a “Messenger” or an “event bus”). This is a pattern where the raiser of an event and the handler of an event do not need to be coupled to each other at all. When an “event” happens in your code that needs to be handled elsewhere, simply post a message to the event aggregator. Then the code that needs to respond to that message can subscribe and handle it, without needing to worry about who is raising it.
An example might be that you send a “help requested” message with details of where the user currently is in the UI. Another service then handles that message and ensures the correct page in the help documentation is launched in a web browser. Another example is navigation. If your application has many screens, a “navigate” message can be posted to the event aggregator, and then a subscriber can respond to that by ensuring that the new screen is displayed in the user interface.
As well as radically decoupling publishers and subscribers of events, the event aggregator also has the great benefit of creating code that is extremely easy to unit test.
9. Use Async and Await for threading
If you are targeting .NET 4 and above and using Visual Studio 12 or newer, don’t forget that you can make use of the new async and await keywords which will greatly simplify any threading code in your application, and automatically handle the marshalling back onto the UI thread when the background tasks have completed. They also hugely simplify handling exceptions across multiple chained background tasks. They are a great fit for Windows Forms applications and well worth checking out if you haven’t already.
10. Don’t leave it too late
It is possible to retrofit all the patterns and techniques I described above to an existing Windows Forms application, but I can tell you from bitter experience, it can be a lot of work, especially once the code behind for forms gets into the thousands of lines of code territory. If you start off building your applications with patterns like MVP, event aggregators and the command pattern you’ll find it a lot less painful to maintain as they grow bigger. And you’ll also be able to unit test all your business logic which which is critical for on-going maintainability.
Comments
Hi Mark!
Fredi MachadoNice post, thanks!
When will your pluralsight course be available? I'm very interested because I'm developing a huge application in WinForms with MVP but we still struggle with some things like MessageBox.Show and Exception handling. I found some tutorials over the internet but with very simple examples using just one form and no user controls. Will you cover a more complete implementation?
Thank you very much!
thanks Fredi, I'm hoping Pluralsight will publish it really soon now. In the course I refactor an existing application to MVP (and other patterns). The down-side of taking this approach is that the end result is that I just have one presenter for many views. I've since refactored the code to be one presenter for each view, and I might share that code somewhere once the course goes live. But hopefully there is enough in there for you to pick up the principles of MVP in a non-trivial application.
Mark HeathGreat mister !
kiquenet kiquenetAny real application with full source code using GOOD PATTERNS for Windows Forms ? Maybe in github ?
good question! Probably not too many of those about since many WinForms apps predate GitHub! I'll let you know if I find any.
Mark HeathI have find a complex Windows Forms real app by Microsoft: Azure-Media-Services-Explorer Tool
kiquenet kiquenetI don't know if is Windows Forms Best Practices.
Rarely Microsoft not create WPF app, and create Windows Forms app.
https://github.com/Azure/Az...
Azure Media Services Explorer (AMSE) is a Winforms/C# application for Windows that does upload, download, encode and stream VOD and live content with Azure Media Services.
Contact: amse@microsoft.com
This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact opencode@microsoft.com with any additional questions or comments.
did you know any App or very helper source code to WinForms ? like awesome WinForms
kiquenet kiquenethuge app sample?
kiquenet kiquenetwhich Pluralsight course?
kiquenet kiquenetMy Windows Forms best practices course - https://pluralsight.pxf.io/...
Mark HeathI'm afraid I don't have a huge open source WinForms sample I can share.
Mark Heathbest practices without REAL WORLD ?
kiquenet kiquenetNot sure what you mean? All these suggested best practices come from my real world experience with WinForms.
Mark HeathThere are no tests in the course? How do you know it works correct and is testable?
Leszek SalfordIn the course I suggest the Model View Presenter pattern as a good way to separate UI code from business logic. The business logic would then be easily unit testable as it is decoupled from UI components. Your UI components also could then be hosted easily in a test harness if you wanted to perform any kind of visual testing of them outside of your application.
Mark HeathHey Mark. I'm developer on WinForms project whose codebase is terrible, it's like tumor. I have nobody to ask for help, good examples nothing since every other developer in company doesn't care and just write spaggeti.
MatejMy biggest concern is if you can provide example on how to use MVP with UI that use Binding class on models. From your examples it doesn't seems clear how to implement on this.
Sorry for my bad english (not native).
hi Matej, it's been a long time now since I did winforms development, but here's something else I wrote on the MVP pattern and includes a very simple demo app https://www.markheath.net/p...
Mark Heath