11

I'm a C++ developer who has been using the MVC pattern to design GUIs ever since.

Recently I wanted to get back into C#, and I set up a Windows Forms application, but now I am a little bit lost on how to push it to a MVC compliant structure.

What I am currently trying to do is to "declare" the class I am given for the WinForms as a View, and add a class for the Model and Controller in the background. However, I am not really sure on how to interact with events, like a button click. Usually I would redirect these events to the controller, and perform an action on the View once I am done.

This feels pretty unsatisfying in this constellation though. For example, if I wanted to implement an "Exit" button, I would have to redirect the event from the View to the Controller, as well as implement an extra public method in my View which can then be called from the Controller, while I could simply call Close() from the View in the first instance.

Do you have any advice for me? Is my understanding of Windows Forms in C# just not good enough yet to try out a MVC implementation? Am I giving the forms class a wrong role? Is MVC simply an inappropriate architecture for this use case?

  • 5
    Model-View-Presenter (MVP) is a more suitable pattern for Windows Forms. See winformsmvp.codeplex.com. – Robert Harvey Jun 28 '17 at 19:45
  • 1
    OT question. But why WInforms? WPF was meant to replace Winforms and supports MVC (Well technically MVVM). Although I will say that the WPF learning curve can be steep. (but if you do it badly you can make WPF code look like Winforms) – Peter M Jun 28 '17 at 20:02
  • 2
    To paraphrase @whatsisname's comment, WPF is geared towards larger applications. Smaller applications might be better served with Winforms because Winforms is arguably simpler. However, if you want to make that argument, you could also make the argument that your Winforms application is small enough where it probably doesn't need MVP anyway. MVVM is baked into WPF. WPF has vector-based graphics, so it doesn't suffer the same sizing problems that Winforms does. WPF is very composable (you can easily put controls inside other controls), and it's got killer data binding. What's not to like? – Robert Harvey Jun 28 '17 at 20:53
  • 2
    @whatsisname WPF may suck, but it sucks a lot less than Winforms for anything over and above a toy program – Peter M Jun 28 '17 at 20:53
  • 2
    For in-house desktop programs, I'd say so. But many companies prefer browser-based applications for their business operations, simply because there is no installation required. – Robert Harvey Jun 28 '17 at 21:14
3

Coincidentally, I am working on a WinForms project that is patterned after MVC. I wouldn't call this a perfect answer, but I will explain my overall design and hopefully this can help you come up with your own.

Based on the reading I did before starting this project, there seems to be no "right" way to implement this. I followed simple OOP and MVC design principles and the rest was trial and error as I developed a workflow.

Is MVC simply an inappropriate architecture for this use case?

No..? There is not enough context in your question to provide a direct answer to that. Why are you using MVC in the first place? What are the non functional requirements of your project? Is your project going to be very UI heavy? Do you care more about security and would rather a layered architecture? What are the main components of your project? Perhaps each component needs a different design pattern. Find out why you want to use this design pattern in the first place and you might answer your own question ;)

My reason for using MVC: its a fairly simple design pattern to understand in my opinion and my design is based heavily on user interaction. The way MVC allows the developer to seperate concerns is sufficient for my application as well. This makes my code alot more maintainable and testable.

I also suppose I am using more of a hybrid design. Usually, the ideal concept presented in software engineering does not actually play out in practice. You can modify the design to suit the needs of your project. No need to get caught up in what is right or wrong. There are general practices, but rules can always be bent or broken as long as you don't shoot yourself in the foot.

My implementation started with a high level design which gave me an idea on what components I will need. Its best to start this way and work your way down in the architecture. Here is the package diagram for the project (created in StarUML): enter image description here

Notice every single layer except the presentation layer depends on the Messaging System. This is a common "language" which the lower layers and sub systems of those layers use to communicate with each other. In my case, it was a simple enumeration based on operations that can be performed. Which brings me to the next point...

Think of operations or commands as the basis for your implementation. What do you want your application to do? Break it down into the most fundamental operations. For example: CreateProject, WriteNotes, SaveProject, LoadProject etc. The GUI (or Form classes) are going to have some event occur (like a button press). Every operation has a controller method associated with it. In this case something like Exit is too simple. The application can simply be closed from the Form class. But suppose I wanted to persist some application data to a file first? I will call the "Save" method from the respective controller class within my button press method.

From there, the controller will call the correct set of operations from the Service classes. The service classes in my application act as an interface to the domain layer. They will validate input received from the controller method call (and thus from the GUI) and manipulate the data model.

Once validation and the corresponding object manipulation is complete, the service method will return a message code to the controller. For example, MessageCodes.SaveSuccess. Both the controller and service classes were based on the domain objects and/or the general set of operations that can be grouped together.

For example: FileMenuController (operations: NewProject, SaveProject, LoadProject) -> ProjectServices (CreateProject, PersistProjectToFile, LoadProjectFromFile). Where Project would be a domain class in your data model. Controller and Service classes in my case were non-instantiable classes with static methods.

Then, the controller recognizes the operation as completing un/successfully. Now, the controller has its own messaging system which it uses to interact with the presentation layer, hence the double dependency between the Service and Presentation layers. In this case a class called ViewState in the ViewModels package is always returned to the GUI by the controller. This state contains information like: "is the state you tried to put the application in valid?", "a human readable message about the operation you tried to perform and why it was or was not successful (error messages)" and a ViewModel class.

The ViewModel class contains relevant data from the domain layer that the GUI will use to update the view. These view models look like the domain classes but in my case I used very skinny objects. Basically they have pretty much no behaviour, just relay information about the lower level state of the application. In other words, I am NEVER going to give away my domain classes to the presentation layer. This is also why the Controllers and Services packages split the service layer into two parts. Controllers will never handle domain classes or validate their state. They just act as a boundary converting GUI relevant data into domain relevant data the services can use and vice -versa. Including the service logic in the controller would lead to very fat controllers, which are harder to maintain.

I hope this gives you a starting point.

protected by gnat Mar 30 at 4:36

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.