C# and QML

A Qt Bridge for C# application has two halves with different jobs: C# holds your application logic, and QML describes the user interface. Understanding how they connect makes the rest of the bridge predictable.

Two languages, two jobs

A Qt Bridge for C# application has two halves that each do a different job:

  • C# holds your application logic: state, data, rules, and the kind of code you already write in any .NET application.
  • QML describes the user interface: what is on screen, how it is laid out, and how it responds to input and animation. Qt Quick is the engine that renders that interface.

You keep writing application logic the way you already do in C#. QML takes over the part that is usually the most tedious to hand-code: describing and animating a UI. The bridge is the connective layer that lets values, objects, and collections in your C# code show up and update inside the QML side.

How your C# becomes visible to QML

Qt Bridge for C# exposes selected C# types, objects, properties, methods, and models to QML. By default, your project's public types are visible; attributes such as [Qt.Ignore] and [Qt.Include] let you fine-tune exactly what QML can see. The first build produces the bridge information and native support needed by the QML side, so this surface is ready by the time your app runs — you do not write any of that glue yourself.

Once a C# type is exposed, you can use it directly in QML: write its name with curly braces to create an instance, then read or set its properties and call its methods — the same way you would use one of QML's own built-in items, such as Text or Button, in the example below.

public class Counter
{
    public int Value { get; set; }

    public void Increment() => Value++;
}
Counter {
    id: counter
}

Text {
    text: "Value: " + counter.value
}

Button {
    text: "Increment"
    onClicked: counter.increment()
}

Notice that QML uses camelCase for the members that C# exposes as PascalCase. This follows QML's own naming convention, so the type feels native on the QML side.

Keeping the UI in sync: properties and notifications

A plain property like Value above is read once. If your C# code changes it later and you want the UI to update automatically, implement INotifyPropertyChanged. QML treats a notifying property as a binding source: whenever your code raises the change notification, every binding that depends on that property re-evaluates.

This is the same pattern you would use to drive a WPF or MVVM-style binding — the difference is that the binding target is now a QML item instead of a XAML control.

Exposing collections and data as models

QML views such as ListView and TableView, as well as simpler model-driven items like Repeater, all expect a model to iterate over. You have two ways to provide one:

  • Use an existing .NET collection. Static collections and observable collections such as ObservableCollection<T> can be exposed directly as models. This is often enough when your data is a straightforward list that QML mostly needs to display.
  • Derive from a model base type. When you need full control over how items are counted, fetched, edited, or grouped — for example paging, lazy loading, or hierarchical data — derive from ListModel<T>, TableModel<T>, or Model and implement the item-count and item-lookup members yourself.

A small list model looks like the C# you already write, with a couple of bridge-aware notification calls around the parts that change:

public class NameList : ListModel<string>
{
    private List<string> Names { get; } = new();

    public void Add(string name)
    {
        BeginInsertItems(Names.Count, Names.Count);
        Names.Add(name);
        EndInsertItems();
    }

    public override string Data(int index) => Names[index];
    public override int ItemCount() => Names.Count;
}
NameList {
    id: names
}

ListView {
    model: names
    delegate: Text {
        required property string item
        text: item
    }
}

BeginInsertItems/EndInsertItems and the other Begin/End pairs on the model base types bracket a structural change — an insert, removal, move, or reset. Calling them tells QML's views exactly which items are about to appear, disappear, or move, so the view can update just those items instead of redrawing everything.

Choosing between built-in support and a custom model

Start with a plain collection or an ObservableCollection<T>. Reach for a custom model only once you need behavior the built-in support does not offer, such as paging, custom sorting, or item editing. Moving from one to the other later is a localized change: the QML side keeps binding to a model the same way.

Where to go from here

Now that you have the mental model, choose the next page based on what you want to do: