Writing GUI apps for Windows is painful

Edit 7/1/2024: I feel like I should edit this article and clarify a few things since someone posted this article on Hacker News, and it appears it has received over 440 comments. This means it has left the typical reader base of this blog (four and a half people). This whole article was written because I was trying to write a companion program to one of my kernel utilities. It’s not meant to be used by more than a few people (no need for excessive accessibility options). I will also use a custom code virtualizer/obfuscator on it, hence the requirement for native code and full control over the compiler toolchain (that’s why Embarcadero’s C++Builder is not mentioned). I think I somehow accidentally managed to write a cool clickbait article because if you take a single one of those requirements below out, at least one of those libraries will fit you perfectly.


For the past few days, I have been trying to find a library that would allow me to write programs with a GUI in C++. My requirements were pretty straightforward:

  • Only Windows support required
  • Commercial use allowed
  • Easy styling, including dark mode
  • The result should be a single .exe file with no or minimal dependencies and a size of less than 40MB
  • Writing the GUI part of the program should not take more time than the actual functionality

WinUI 3

At first glance, it looks like an excellent choice. It allows you to use modern Windows components while also letting you customize the styling colors. For design, you can use XAML, which is super easy to grasp, or you can just use the Visual Studio designer directly.

screenshot

(WinUI 3 controls gallery)

Problem: Shipping the app in unpackaged form is not well supported. Most of the time when I have tried moving the app to a VM or a different computer, it fails to launch due to some obscure dependencies missing. To make it worse, you need to supply a bunch of .dll files that handle the WinUI functionality. There is no way to have a single portable .exe file. Using packaged form usually works without any issues, but they are installed as AppX packages which brings many issues on its own (especially if you need access to all Win32 APIs).

Win32 / MFC / small libraries wrapping Win32

I need high portability, so it would make sense to use the OS’s native rendering. Such a program could be a single .exe file (given that we statically link MFC) and would also be super small (just a few kilobytes). I could also use a more minimal library that someone has already written, which means it would be really easy to get from concept to working app fast.

screenshot

(Basic Win32 form)

Problem: It is extremely hard to stylize native Win32 controls. It would require me to write a custom paint function for every single control, which would take so much time I could raise a family in the meantime. There is a “hidden” dark mode for Win32 controls used by Windows File Explorer that you can activate, but it covers only some of the controls and still doesn’t look good.

Qt

This library is the holy grail of C++ GUI. While it’s quite complex, it offers easy styling with Qt Style Sheets, which use a language similar to CSS.

screenshot

(OBS studio is using Qt and custom stylesheets)

Problem: When linking dynamically, there are a myriad of different .dlls required to run the app, totaling over 40MB. You can statically link Qt into your program, which will drastically reduce the size (since the unused parts are removed), but then you must either make it open-source or distribute object files for recompilation due to Qt’s LGPL license. Alternatively, you can buy a commercial license for several thousand dollars.

wxWidgets

Quite an easy-to-learn library with the option to use wxFormBuilder. It has a more permissive license than Qt and can be statically linked into a 3MB executable.

screenshot

(wxWidgets with experimental Windows dark mode option enabled)

Problem: On Windows, this library uses native Win32 components and offers no styling options (since we cannot easily overwrite the paint functions, it’s even worse than using Win32/MFC directly). It supports applying Windows File Explorer dark controls, but again, they kinda suck.

hikogui

Quite new retained mode GUI library using Vulkan as a backend. Has built-in dark mode and is quite easy to style yourself.

screenshot

(Screenshots from official repository)

Problem: In order to compile it successfully, you will need a PhD in computer science with a specialization in compiler development. After trying to compile the example for more than 30 minutes (including different branches and release tags), the only thing I got was an executable that would immediately crash with an access violation inside some Vulkan library, so I just gave up. It looks really promising even though I don’t really like the heavy use of obnoxious STL (sometimes it’s not even necessary).

Sciter

Actually a good alternative to Electron that allows you to use HTML/CSS to write the GUI for your desktop app.

screenshot

(Example of bad antialiasing on svg icons)

Problem: You might think that the issue is going to be size, but actually, the final app with all .dlls is around 25MB, which is completely fine with me. It would be even better if it were actually open-source and you could use the statically linked version for commercial use (same issue as with Qt). Since it’s not as expensive as Qt, though ($310 currently for an Indie license), I would pay the money and be happy. The issue is that, as you can see in the image above (look at the titlebar icons), the rendering is not that great. I was having all sorts of antialiasing issues with fonts and images (high DPI option was enabled and the issue was present even in the precompiled scapp.exe). Also, no matter what you do, the window will have a quite thick (2-3px) grey frame that you just cannot customize or modify at all.

WinForms / WPF

If you ask about C++ GUI libraries for Windows on some random forums, most of the time you will be told that it’s a bad idea (not arguing with that), and that you should instead write the frontend of the program in some other stack and then just load your functionality written in C++ as a component/module. This will allow you to easily stylize it and speed up the development significantly. Technically, it is possible to have a single .exe file with a small size and use WinForms/WPF. There are two ways we can go about it:

  1. Bundle the .dll as a resource into the app and make it extract it to some temporary folder, then use P/Invoke and call the compiled .dll from within the C#/.NET app.
  2. Use C++/CLI.

screenshot

(DarkUI for WinForms)

Problem: The .NET framework comes preinstalled on Windows 10+, so we would technically still meet the no dependencies criteria. The issue is that with bundling the .dll, it would still mean it being extracted somewhere and writing additional code for the P/Invoke to work, and C++/CLI gets compiled to .NET IL code, in other words, you can open the resulting app in dnSpy and see the C++ code translated to C# equivalent (which is not what I want, I want native code).

Solution?

Those were only a few options that I considered. After a very long time trying out all sorts of different libraries and at one point even writing my own MFC styles, I figured out that for simple apps there is simply nothing better suited than Dear ImGui.

It has some disadvantages, mainly when trying to design complex UIs and that it’s not a retained mode UI but rather an immediate mode UI, so we have to run a GPU renderer like DirectX to render 60 or more frames per second just for the UI.

It matches all the other points though, since DirectX is included by default on modern Windows versions.

screenshot

(ImGui AppKit example project)

I have written an example that you can see above of how you can use the built-in multi-viewports functionality to use it to make simple GUI apps.

screenshot

(ImGui AppKit compiled app size)

The compiled program has only 500KB in size and does not require the installation of anything, not even VC++ redistributables if you statically link MFC into it.

Note: I am writing this article while it’s 32°C inside my room. As a fellow European, I do not have air conditioning. Please forgive any grammatical errors or poorly structured sentences.