TypePHP\Qt
Guide
Widgets
Advanced
Reference
FAQ
  • English
  • 简体中文
GitHub
Guide
Widgets
Advanced
Reference
FAQ
  • English
  • 简体中文
GitHub
  • Guide

    • Guide
    • Installation
    • Installing and Building Qt
    • Quick Start
    • Project Structure
    • Architecture
    • State and View
  • The UI

    • Events and Handlers
    • Properties
    • Layout
    • Dialogs and System Integration
    • Multiple Windows, Tray and Timers
    • Patching

Patching

patch() is the imperative bypass of the declarative view, for hot paths that change many times a second — a log stream, a progress refresh — where rebuilding the whole tree every frame is wasteful.

$app->patch([
    ['op' => 'set',  'id' => 'status', 'props' => ['text' => 'Done']],
    ['op' => 'call', 'id' => 'log', 'method' => 'appendRows',
     'args' => [[['10:32', 'started'], ['10:33', 'ready']], ['l1', 'l2']]],
]);

Each operation is one of two shapes: set changes properties, call invokes a method.

set — change properties

props follows exactly the same semantics as declarative properties:

$app->patch([
    ['op' => 'set', 'id' => 'progress_bar', 'props' => ['value' => 65]],
    ['op' => 'set', 'id' => 'hint', 'props' => ['text' => 'Working…', 'visible' => true]],
]);

Structural fields (rows / columns / row_ids / nodes / headers) trigger a full table/tree rebuild and preserve the selection by row id:

$app->patch([
    ['op' => 'set', 'id' => 'tbl', 'props' => [
        'rows'    => [['B', '2'], ['A', '1']],
        'row_ids' => ['r2', 'r1'],
    ]],
]);

call — invoke a method

args are positional. Six methods are implemented:

methodargsScope
appendRowsargs[0] = row list (each row is a list of cells), args[1] = optional row id listtable only
clearnoneEmpties a table, a tree/list/combo's items, or a text control
setTextargs[0] = textlabel button lineedit textedit checkbox radio
setValueargs[0] = valueNumeric for progress/slider/spin, text for input controls
selectargs[0] = id or indexExactly the same semantics as that control's current property
focusnoneGive keyboard focus to the control
$app->patch([
    ['op' => 'call', 'id' => 'log',  'method' => 'appendRows', 'args' => [[['10:32', 'started']]]],
    ['op' => 'call', 'id' => 'body', 'method' => 'setText',    'args' => ['new content']],
    ['op' => 'call', 'id' => 'bar',  'method' => 'setValue',   'args' => [80]],
    ['op' => 'call', 'id' => 'name', 'method' => 'focus'],
]);

The core constraint: it is a bypass, not new state

What call changes only survives until the next render

After execution the changed properties' diff signature is invalidated, so the next render() re-syncs from the tree.

Which means: appended rows and cleared content are only valid until that render. To persist, write back into state.

That gives you a choice:

Keep the data in state (recommended)

// append to state at the same time
$state['logLines'][] = [date('H:i:s'), $line];

// view() builds the full table — a re-render does not lose it
$app->view(function () use (&$state): array {
    return WidgetTree::table(['Time', 'Event'], $state['logLines'], ['id' => 'log_tbl']);
});

patch is only for immediate effect (no waiting for the next frame); state is the source of truth.

Pure patch (only for short-lived content)

$app->patch([['op' => 'call', 'id' => 'log', 'method' => 'appendRows', 'args' => [[$row]]]]);

The next re-render — for any reason, say the user clicks another button — and those rows are gone.

When to use patch

SituationUse patch?
Log stream, progress bar, live numbers✅ dozens of times a second; a full tree rebuild is wasteful
UI change caused by a user action❌ change state and let view() reflect it
Data that must persist❌ write state
Appending one row to a big table✅ but also write back into state

Default to state. Only introduce patch for a spot whose render cost is a measured problem.

Unknown content is silently ignored

An unknown method or an unknown id is silently ignored — consistent with unknown properties. That makes degradation and fault tolerance easy; the price is that a typo will not tell you.

Full example: progress bar plus log

$app->on('start_btn', 'click', function () use ($app, &$state) {
    for ($i = 1; $i <= 100; $i++) {
        // progress: a hot path, go through patch
        $app->patch([['op' => 'set', 'id' => 'progress_bar', 'props' => ['value' => $i]]]);

        // log: write back into state (it must persist)
        if ($i % 10 === 0) {
            $state['logLines'][] = [(string) $i . '%', 'working'];
        }

        $app->runFrames(1);   // give the UI a chance to refresh
    }

    $app->patch([['op' => 'set', 'id' => 'hint', 'props' => ['text' => 'Done']]]);
});

Pump frames inside a long loop

The UI does not refresh by itself while a handler runs — the runFrames(1) above is what makes the progress bar move. Without it, the UI jumps to 100% all at once after the loop ends.

Edit this page on GitHub
Last updated: 10/6/26, 3:13 AM
Prev
Multiple Windows, Tray and Timers