Adding QML to Existing C# Projects
Adding Qt Bridge for C# to an existing project means making a few decisions that a fresh template project handles for you: package selection, startup shape, exposing existing C# types to QML, and packaging application resources.
If you are starting from scratch, see Getting Started, C# and QML and Project Templates first.
Requirements
Before you add the bridge to an existing project, confirm that the project meets these requirements:
- .NET SDK 8+
- Windows x64 or Linux x64
- CMake and Ninja available on
PATH - A C++ toolchain (Visual Studio Build Tools on Windows,
build-essentialon Linux) - On Linux: a Qt 6 installation whose prefix contains
lib/cmake/Qt6/Qt6Config.cmake
CMake, Ninja, and the C++ toolchain are used by the bridge build behind the scenes; you do not need to write CMake files or C++ code yourself.
Add the package
Choose the package that matches your target platform.
Visual Studio
Open the NuGet Package Manager for your project (Tools > NuGet Package Manager > Manage NuGet
Packages for Solution) and search for QtGroup.Qt.Bridge.CSharp. Install the package that
matches your target platform:
QtGroup.Qt.Bridge.CSharp.win-x64for Windows x64QtGroup.Qt.Bridge.CSharp.linux-x64for Linux x64
.NET CLI
From your project folder, run:
# Windows
dotnet add package QtGroup.Qt.Bridge.CSharp.win-x64 --version 0.3.*-*
# Linux
dotnet add package QtGroup.Qt.Bridge.CSharp.linux-x64 --version 0.3.*-*
The package is platform-specific because the bridge includes a native integration layer for the
target platform. The 0.3.*-* version range stays on the 0.3 pre-release train.
Update the entry point
Your Main method needs two calls to start and run the QML side:
using Qt.Quick;
static void Main(string[] args)
{
Qml.LoadFromRootModule("Main");
Qml.WaitForExit();
}
Qml.LoadFromRootModule("Main")loadsMain.qmlas the root QML component and creates the window it describes. The string must match the QML file name without the.qmlextension.Qml.WaitForExit()blocksMainuntil the QML engine shuts down, keeping the process alive while the UI runs.
If your application has background work to do while the UI is running, use a finite timeout and loop:
Qml.LoadFromRootModule("Main");
while (!Qml.WaitForExit(100))
{
// update data, poll queues, or tick background state
}
Add QML files
Add at least one .qml file to the project directory. The build picks up every .qml file in
the project tree automatically — no project file entry is needed. The name you pass to
LoadFromRootModule must match the file name without the .qml extension.
For a minimal starting point, Main.qml can be as simple as:
import QtQuick
import QtQuick.Controls
ApplicationWindow {
visible: true
width: 640
height: 480
title: "My App"
}
Build
On Windows, build with:
dotnet build
dotnet run
On Linux, point QtDir at your Qt installation:
dotnet build -p:QtDir=/path/to/qt-prefix
dotnet run -p:QtDir=/path/to/qt-prefix
The first build produces the bridge information and native support needed to make your C# types visible to QML.
Expose existing C# types to QML
By default, all public types defined in your project are exposed to QML after the first
successful build. You do not need to opt types in one by one.
QML uses camelCase for property names and method names, while your C# code keeps PascalCase.
This mapping is automatic; a property named UserName in C# is userName on the QML side.
For a C# type to update the QML side when its state changes, implement
INotifyPropertyChanged.
QML treats a notifying property as a live binding source: whenever your code raises the change
event, every QML binding that reads that property updates automatically. This is the same pattern
you use with WPF or any MVVM binding, with QML items as the binding targets instead of XAML
controls.
See C# and QML for a walkthrough of how a C# type, its properties, and its methods appear on the QML side.
Fine-tune what QML can see
If the default public-type surface is too broad, use
[Qt.Ignore],
[Qt.IgnoreType], and
[Qt.Include] to adjust it.
Exclude a type defined in your project:
[Qt.Ignore]
public class InternalHelper { }
Exclude a single property or method:
public class UserProfile
{
[Qt.Ignore]
public string PasswordHash { get; set; }
public string DisplayName { get; set; }
}
Exclude types by name at assembly level, including types from external assemblies you do not own:
[assembly: Qt.IgnoreType(typeof(List<int>))]
[assembly: Qt.IgnoreType("Some.ThirdParty.InternalType")]
Exclude an entire type hierarchy and opt specific subtypes back in:
[assembly: Qt.IgnoreType(typeof(BaseClass), Inherited = true)]
[Qt.Include]
public class SpecificSubclass : BaseClass { }
For the full attribute reference and all filtering patterns, see the API Reference.
Use existing collections as QML models
QML views such as ListView and
Repeater need a model. You can use your
existing .NET collections directly:
- Plain arrays and
List<T>work as static models. ObservableCollection<T>works as a live model that tells the view about additions and removals.
Expose a collection as a property on any bridged type and bind the view to it in QML:
public class AppState : INotifyPropertyChanged
{
public ObservableCollection<string> Items { get; } = new();
// ...
}
AppState {
id: app
}
ListView {
model: app.items
delegate: Text { required property string item; text: item }
}
When you need control over paging, lazy loading, sorting, or hierarchical data, derive from
ListModel<T> or
TableModel<T> instead.
See C# and QML for guidance on choosing between built-in collection support
and a custom model.
Package application resources
Images, fonts, JSON files, and other assets that your QML interface needs must be packaged into
the Qt resource store. QML and C# both address them with qrc:/ URLs at runtime.
Add a <QtResource> item for each file or glob you want to package:
<ItemGroup>
<QtResource Include="images\logo.svg" />
<QtResource Include="images\*" />
<QtResource Include="fonts\Inter.ttf" />
<QtResource Include="data\config.json" />
</ItemGroup>
The default URL uses your assembly resource ID and the file path from your project:
qrc:/assemblies/<AssemblyId>/<relative-path>
For a project named MyApp, images\logo.svg becomes:
qrc:/assemblies/MyApp/images/logo.svg
Reference that URL from QML wherever QML expects a URL:
Image {
source: "qrc:/assemblies/MyApp/images/logo.svg"
}
Use Qt.Resources when C# code needs to read the same packaged files:
string json = Qt.Resources.ReadAllText("qrc:/assemblies/MyApp/data/config.json");
byte[] bytes = Qt.Resources.ReadAllBytes("qrc:/assemblies/MyApp/images/logo.svg");
For application icons, fonts, .resx discovery, access modes, alias overrides, referenced project
resources, and localization guidance, see Application Resources.
Check your setup
After the first successful build, confirm that:
- The project builds without missing toolchain errors
dotnet runlaunches the application and a window appears- QML files are included in the project and visible at runtime
- Changes to exposed C# properties update the QML UI
Common issues
If the build fails or the window does not appear, check these areas first:
- The .NET SDK version is older than .NET 8
- The C++ toolchain is not installed or not on
PATH - CMake or Ninja is missing
- On Linux,
QtDiris not set or points to the wrong prefix - The
.qmlfile is outside the project directory and not picked up by the build - The name passed to
LoadFromRootModuledoes not match the.qmlfile name - The first build has not completed yet; QML-facing C# types and editor support require a successful build
- A
qrc:/URL in QML returns nothing — check that the file has a<QtResource>item and that the path matchesqrc:/assemblies/<AssemblyName>/<relative-path>
Where to go from here
- See C# and QML for a deeper look at the mental model behind how C# and QML connect.
- See Editing QML in Visual Studio for QML diagnostics, completion, and semantic editor support after your first build.
- See Application Resources for resource packaging,
qrc:/URLs, and theQt.ResourcesAPI. - See the API Reference for the complete attribute, model base type, and startup API documentation.