Revisiting Microsoft Forms: WinForms

This is a series of posts on older Microsoft forms technologies and reflections on what is really good about them. When I first used these platforms, I had strong biases against them, which were encouraged by co-workers and friends. Having spent over a decade building software in .NET, I’ve come to appreciate at least certain aspects of these tools, some of which are moving forward to .NET 5. Windows Forms, or WinForms, is one of those platforms, and I would like to spend some time talking through some really nice aspects of the framework.

You can find the previous post on WebForms here, if you missed it.

The Bad

I never really had many gripes with WinForms except for its very plain, “battleship gray” theme that seemed somewhat insurmountable to replace. I’ve since seen some fantastic efforts to fix this, e.g. MetroFramework, Winform.Metroframework, and MetroModernUI.

Forms are also the driver for WinForms applications. It’s in the name, after all, but the concept of an application as a form has always struck me as strange. I greatly prefer the terminology used in WPF and other platforms, where a form is the thing into which you enter data.

Lastly, I never really loved the code-behind model that was the default when working with the designer. The generated code was pretty messy, and the code-behind approach made things difficult to test. As I noted in the last post, I have since learned how to extract and test logic independently of how it was wired up to framework code, so I no longer consider the testability issue of much concern.

The Good

Unlike WebForms, this fit the OO model I was learning when I first started with .NET. It keeps things simple and provides a consistent and easy composition model with .Controls.Add, and sticks to .NET’s core eventing model to provide callbacks. WinForms is therefore far simpler to think about than, for example, WPF, which is more powerful but also provides other means of triggering events and UI updates.

I think the first WinForms app I wrote that was not based on a tutorial was from Tomas Petricek‘s Real World Functional Programming using F#. I later learned that you can create and run a Form directly from F# Interactive:

#r "mscorlib.dll"
#r "System.dll"
open System
open System.IO
open System.Windows.Forms
let mutable counter = 0
let clickCount = new Label()
let message = new Label()
let button = new Button(Text = "Click me")
let clickHandler =
EventHandler(fun (sender: obj) (args: EventArgs) ->
message.Text <- sprintf "Hello from %A" sender
counter <- counter + 1
clickCount.Text <- sprintf "Clicked %d times" counter)
let layout = new FlowLayoutPanel(Dock=DockStyle.Fill)
let form = new Form()

view raw


hosted with ❤ by GitHub

NOTE: with F# Interactive now running as a dotnet tool, I wondered whether it would continue to be possible given WinForms has moved to the Microsoft.WindowsDesktop.Sdk framework reference. Phillip Carter, Microsoft Senior Program Manager for F#, responded that this would no longer be possible in the dotnet fsi (.NET Core F# Interactive):

Aside from mapping very well to OO design, it is quite flexible. Aside from being able to launch it from a script or code it from scratch starting with a console application, you can do also implement many more design patterns, such as MVP or MVU.

#r "mscorlib.dll"
#r "System.dll"
open System
open System.Windows.Forms
type Update<'Msg, 'Model> = 'Msg -> 'Model -> 'Model
type Dispatch<'Msg> = 'Msg -> obj -> EventArgs -> unit
type View<'Model, 'Msg> = 'Model -> Dispatch<'Msg> -> Control
type Program<'Model, 'Msg>(initialModel:'Model, view:View<'Model,'Msg>, update:Update<'Msg,'Model>) =
let pump = Event<'Msg * 'Model>()
let evt = pump.Publish
let dispatch model msg _ _ = pump.Trigger(msg, model)
let form = new Form()
do evt.Add(fun (msg, model) ->
let newModel = update msg model
let newLayout = view newModel (dispatch newModel)
member __.Run() =
let initialLayout = view initialModel (dispatch initialModel)
type Model = int
let initialModel : Model = 0
type Msg = Increment | Decrement
let update msg model =
match msg with
| Increment -> model + 1
| Decrement -> model 1
let view model dispatch =
let clickCount = new Label(Text=sprintf "Clicked %d times" model)
let incrButton = new Button(Text="+")
let incrClickHandler = EventHandler(dispatch Increment)
let decrButton = new Button(Text="")
let decrClickHandler = EventHandler(dispatch Decrement)
let layout = new FlowLayoutPanel(Dock=DockStyle.Fill)
layout :> Control
let runProgram model view update =
let main = Program(model, view, update)
runProgram initialModel view update

Video demo of the counter app from above

This flexibility to adapt to different UI architectures strikes me as something Don Syme referred to as “Object Programming” in his Code I Love talks. In any case, WinForms is in many ways very much like React in that it provides a way to render a UI and leaves the details of how to do that to the implementer.

Lastly, to parallel the last bit from The Bad section, the designer was actually a nice feature that worked quite well. Although I noted how I dislike the code-behind model above, I like that it feels a little like Smalltalk, where you compose the UI with visual objects and then fill in the details with what chunks of code. It’s more or less the same thing. Had I put that together sooner, I don’t think I would have given the code-behind model such a hard time.

Final Thoughts

I think WinForms has stood the test of time, aside from its strict adherence to battleship gray as its primary and dominant appearance. I wouldn’t choose to build many apps in it, but it’s consistency and simplicity make it a terrific platform for quick solutions and adapts really well to any architectural style if you just need something simple to prototype a solution.

What do you think? Was I too easy on WinForms? Is there more to learn from this framework? Leave a comment below!