DEV Community

xiang,xin huang
xiang,xin huang

Posted on AI-assisted

Modernizing Qt Widgets without a QML rewrite

I maintain Fluent-Qt, an open-source C++ component library for Qt Widgets. The project started from a practical constraint: a mature desktop application may need a more modern interface, but rewriting the whole UI stack is often the least realistic way to get there.

This is not an argument that Widgets are better than QML. QML is the right choice for many new applications. The narrower question is: how can an existing Widgets application modernize one screen at a time without replacing the parts that already work?

Fluent-Qt Gallery showing a Qt Widgets desktop interface

A UI rewrite changes more than pixels

A production Qt Widgets application usually contains years of decisions around:

  • QObject ownership and signal/slot wiring
  • custom models, delegates, and validation
  • native window behavior and platform workarounds
  • CMake targets, packaging, and deployment
  • accessibility, keyboard navigation, and input methods

Moving all of that to another UI layer is not merely a visual refresh. It is a migration of behavior, test coverage, and operational knowledge.

So I use a smaller migration boundary: preserve the application object model, event loop, and build workflow, then replace the visible layer only where the change is useful.

The incremental boundary

For a one-screen modernization to be credible, I think it should satisfy four constraints:

  1. Existing QWidget layouts and application-owned models can remain in place.
  2. The application keeps its normal QApplication, signals, slots, and events.
  3. The library links as an ordinary CMake target rather than introducing a parallel runtime.
  4. Old and new controls can coexist while a screen is migrated.

Fluent-Qt follows that boundary. Its reusable library depends on Qt Widgets and exposes C++ controls for input, navigation, collections, data grids, overlays, and windows. The current baseline is C++17 with Qt 5.15+ or Qt 6.2+.

The smallest useful integration

For a new experiment, the library can be pulled in with FetchContent:

include(FetchContent)

FetchContent_Declare(
    fluentqt
    GIT_REPOSITORY https://github.com/calvinhxx/Fluent-Qt.git
    GIT_TAG v1.7.5
    GIT_SHALLOW TRUE
)

FetchContent_MakeAvailable(fluentqt)
target_link_libraries(my_app PRIVATE FluentQt::FluentQt)
Enter fullscreen mode Exit fullscreen mode

The controls then participate in an ordinary Widgets layout:

#include <FluentQt/FluentQt.h>

auto* button = new fluent::basicinput::Button(
    QStringLiteral("Save changes"), parent);

button->setFluentStyle(
    fluent::basicinput::Button::Accent);

layout->addWidget(button);
Enter fullscreen mode Exit fullscreen mode

The button itself is not the interesting part. The important point is what does not change: the parent is still a QWidget, the layout is still a Qt layout, and the control can connect to existing application slots.

add_subdirectory() and installed-package find_package(FluentQt CONFIG REQUIRED) integrations are also supported, so a team can choose the boundary that matches its dependency policy.

Evaluate the controls before installing anything

I compiled the C++ Gallery to WebAssembly so the component set can be explored directly in a browser:

👉 Open the live WebAssembly Gallery

The Gallery is useful for checking component coverage, themes, states, and interaction ideas. It also makes the project's current surface visible instead of asking people to trust a feature list.

There is an important limitation: a browser build is not proof of native desktop behavior. Window management, screen readers, IME input, drag-and-drop, packaging, and some platform-specific rendering still need verification on Windows, macOS, and Linux. I treat the WebAssembly build as an evaluation surface, not as a substitute for desktop acceptance testing.

What I am trying to learn next

Adding more controls is easy to measure. Proving that a library reduces the cost of modernizing a real application is harder—and more useful.

The next step I want to document is one existing screen migrated in place, including the before/after diff, integration time, platform limitations, and the parts that should have remained plain Qt.

If you maintain a Qt Widgets application, I would appreciate one concrete answer:

Which single screen or interaction is hardest to modernize in your codebase, and what makes it difficult?

My current guesses are dense data views, navigation, overlays and dialogs, or custom window chrome. Real examples would help me choose a better case study than another isolated component demo.

The project is MIT-licensed. Blunt feedback is welcome, especially from people maintaining long-lived desktop software.

Top comments (0)