为 Windows 编写 GUI 应用程序很痛苦
Writing GUI apps for Windows is painful

原始链接: https://tulach.cc/writing-gui-apps-for-windows-is-painful/

本文最初由作者撰写,旨在详细介绍他们为个人项目寻找合适的 C++ GUI 库的过程。 由于它在 Hacker News 上很受欢迎,收到了 440 多条评论,作者决定澄清几个方面。 他们正在为其 Windows 应用程序寻找一种轻量级、易于设计的 GUI 解决方案,最好只需要 Windows 支持、易于设计(包括暗模式)、最小的依赖性和快速的开发时间。 最初,他们尝试了 WinUI 3,但发现缺乏可移植执行能力令人失望。 后来,他们在使用该库为应用程序创建单个可移植 .exe 时遇到了困难。 随后的尝试引导他们探索各种替代方案,例如 wxWidgets、利用本机 Win32 组件的库,甚至尝试创建自己的 MFC 样式。 然而,没有一个能够满足他们的所有要求。 最终,作者发现了 Dear ImGui,一个即时模式的 UI 库,尽管它是非保留模式,但它仍然具有优势。 凭借内置的多视口功能和微小的占地面积,该库提供了令人满意的体验。 编译后的示例名为“SimpleApp”,只有 500 KB,展示了其效率和可移植性。

作者探索了为 Windows 应用程序创建自定义图形用户界面 (GUI) 的各种选项,但由于特定要求,将讨论限制为易于使用的库。 这些条件包括需要完整的自定义 GUI 样式而无需编写个人渲染函数、在 40MB 大小限制下作为可执行文件自包含,以及由于许可问题而避免使用 Qt 等专有解决方案。 作者承认 Qt 可以满足这些条件,但由于许可不兼容而选择不满足这些条件。 作者的结论是,如果放宽对依赖项的限制、更大的下载大小或利用内置的 Windows GUI 控件,情况将会显着不同。 对于没有依赖项和许可许可的轻量级、完全自定义的 GUI 应用程序,作者预测 ImGui 可能是首选解决方案。 然而,作者质疑微软是否在其应用程序中使用这种方法,并表达了对遵循主机系统外观的应用程序的偏好。 作者分享了他们尝试使用 WPF 创建多平台 GUI 的经验,但由于主题和本地化支持不足而放弃。 目前,他们正在评估 Tauri 在处理 Web 视图中的 UI 时提高 Rust 技能的潜力,但与 SwiftUI 相比仍然对结果不满意。
相关文章

原文

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

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.

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.

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.

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

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.

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.

联系我们 contact @ memedata.com