QML
← All components

File Upload

Select files from disk and show the selected files, upload progress and results.

import Syidax.Core.FileUpload

Supported QML types

TypeDefinition / role
File field with browsing, drag-and-drop, validation and upload progress.
Supporting helper that resolves a local file size using Qt.labs.folderlistmodel; normally created by SyFileUpload.

Learn by example

Previews are images of native Qt runs. Use the buttons below each image to view a result.

01 Basic composition

SyFileUpload is a form field with a dashed dropzone and a list of queued files.

Qt capture · image
Basic composition · Default
Default execution
QML code
import QtQuick
import Syidax.Core

Item {
    implicitWidth: Math.max(360, childrenRect.width)
    implicitHeight: childrenRect.height

    property QtObject settings: QtObject {
        id: storyArgs
        property string label: "Attachments"
        property string accept: ""
        property bool multiple: true
        property real maxSize: 0
        property int maxFiles: 0
        property string buttonLabel: "Browse files"
        property string dragLabel: "Drop files here or browse"
        property string helperText: ""
        property bool hideFiles: false
        property string fieldState: ""
        property bool disabled: false
        property bool required: false
        property string message: ""
    }

    

    SyFileUpload {
        width: 420
        label: storyArgs.label
        accept: storyArgs.accept
        multiple: storyArgs.multiple
        maxSize: storyArgs.maxSize
        maxFiles: storyArgs.maxFiles
        buttonLabel: storyArgs.buttonLabel
        dragLabel: storyArgs.dragLabel
        helperText: storyArgs.helperText
        hideFiles: storyArgs.hideFiles
        fieldState: storyArgs.fieldState
        disabled: storyArgs.disabled
        required: storyArgs.required
        message: storyArgs.message
    }

}

02 accept · maxSize · maxFiles

Every file passes metadata/readability and accept/maxFiles/maxSize checks before entering files/formValue.

Qt capture · image
accept · maxSize · maxFiles · Default
Default execution
QML code
import QtQuick
import Syidax.Core

Item {
    component EventLog: Rectangle {
        id: logBox
        property var lines: []
        implicitWidth: 440
        implicitHeight: Math.max(56, logText.implicitHeight + 24)
        radius: 4; color: Theme.semantic.backgroundBold
        function log(value) { lines = lines.slice(-3).concat([String(value)]) }
        Text {
            id: logText; x: 12; y: 12; width: parent.width - 24
            text: logBox.lines.length ? logBox.lines.join("\n") : "Interact with the controls to see event results."
            wrapMode: Text.Wrap; color: Theme.semantic.textDefault
            font.family: Theme.monoFontFamily; font.pixelSize: 12
        }
    }

    implicitWidth: Math.max(360, childrenRect.width)
    implicitHeight: childrenRect.height
    // Focused Gallery routes: keep only the selected API and its prerequisites.
    id: featureStory

    readonly property alias uploadControl: upload

    Column {
        spacing: 12
        SyFileUpload {
            id: upload
            width: 420
            accept: ".svg,application/pdf"; maxSize: 5 * 1024 * 1024; maxFiles: 3; multiple: true
            onRejected: (detail) => output.log(detail.file.name + ": " + detail.reason)
        }
        Row {
            spacing: 8
            SyButton { objectName: "upload-samples"; text: "Add sample files"; variant: "secondary"; size: "small"; onClicked: upload.addFiles([Qt.resolvedUrl("../avatar/portrait.svg"), Qt.resolvedUrl("samples/notes.txt"), Qt.resolvedUrl("../pdf-viewer/docs/quarterly-report.pdf"), Qt.resolvedUrl("../avatar/portrait.svg"), Qt.resolvedUrl("../pdf-viewer/docs/quarterly-report.pdf")]) }
        }
        EventLog { id: output }
    }
}

03 fieldState · disabled

fieldState paints the dropzone border to report the outcome of the app's own upload: "error", "warning" or "success" ("" / "default" for the normal border).

Qt capture · image
fieldState · disabled · Default
Default execution
QML code
import QtQuick
import Syidax.Core

Item {
    implicitWidth: Math.max(360, childrenRect.width)
    implicitHeight: childrenRect.height
    // Focused Gallery routes: keep only the selected API and its prerequisites.
    id: featureStory

    Column {
        spacing: 12
        SyFileUpload { id: featureTarget0; visible: true; width: 420; fieldState: "error"; dragLabel: "Upload failed — drop the files again" }
        SyFileUpload { id: featureTarget1; visible: true; width: 420; fieldState: "success"; dragLabel: "All files uploaded" }
        SyFileUpload { id: featureTarget2; visible: true; width: 420; disabled: true }
    }
}

04 setProgress

The component does not upload.

Qt capture · image
setProgress · Default
Default execution
QML code
import QtQuick
import Syidax.Core

Item {
    implicitWidth: Math.max(360, childrenRect.width)
    implicitHeight: childrenRect.height
    readonly property alias uploadControl: upload

    Column {
        spacing: 12
        SyFileUpload {
            id: upload
            width: 420
            multiple: true
            onSelected: (detail) => detail.files.forEach((file) => story.startUpload(file.id))
        }
        SyButton { objectName: "upload-samples"; text: "Add sample files"; variant: "secondary"; size: "small"; onClicked: upload.addFiles([Qt.resolvedUrl("../pdf-viewer/docs/quarterly-report.pdf"), Qt.resolvedUrl("../avatar/portrait.svg")]) }
    }
    id: story
    property var uploading: ({})
    function startUpload(id) { uploading[id] = 0; ticker.start() }
    Timer {
        id: ticker
        interval: 120
        repeat: true
        onTriggered: {
            let busy = false
            for (const id in story.uploading) {
                if (story.uploading[id] >= 100) continue
                story.uploading[id] += 10
                busy = true
                if (story.uploading[id] >= 100) upload.setProgress(id, 100, id === "f2" ? "error" : "success")
                else upload.setProgress(id, story.uploading[id])
            }
            if (!busy) ticker.stop()
        }
    }
}

05 selected · rejected · changed · removed

Every file passes metadata/readability and accept/maxFiles/maxSize checks before entering files/formValue.

Qt capture · image
selected · rejected · changed · removed · Default
QML code
import QtQuick
import Syidax.Core

Item {
    component EventLog: Rectangle {
        id: logBox
        property var lines: []
        implicitWidth: 440
        implicitHeight: Math.max(56, logText.implicitHeight + 24)
        radius: 4; color: Theme.semantic.backgroundBold
        function log(value) { lines = lines.slice(-3).concat([String(value)]) }
        Text {
            id: logText; x: 12; y: 12; width: parent.width - 24
            text: logBox.lines.length ? logBox.lines.join("\n") : "Interact with the controls to see event results."
            wrapMode: Text.Wrap; color: Theme.semantic.textDefault
            font.family: Theme.monoFontFamily; font.pixelSize: 12
        }
    }

    implicitWidth: Math.max(360, childrenRect.width)
    implicitHeight: childrenRect.height
    // Focused Gallery routes: keep only the selected API and its prerequisites.
    id: featureStory

    readonly property alias uploadControl: upload

    

    Column {
        spacing: 12
        SyFileUpload {
            id: upload
            width: 420
            multiple: true; accept: ".svg,.pdf"
            onSelected: (detail) => { if (true) { output.log(("selected: %1").arg(detail.files.map((f) => f.name).join(", "))) } }
            onRejected: (detail) => { if (true) { output.log(("rejected: %1 (%2)").arg(detail.file.name).arg(detail.reason)) } }
            onChanged: (detail) => { if (true) { output.log(("changed: %1 files").arg(detail.files.length)) } }
            onRemoved: (detail) => { if (true) { output.log(("removed: %1").arg(detail.file.name)) } }
        }
        SyButton { id: exampleControl1;  visible: true; objectName: "upload-samples"; text: "Add sample files"; variant: "secondary"; size: "small"; onClicked: upload.addFiles([Qt.resolvedUrl("../avatar/portrait.svg"), Qt.resolvedUrl("samples/notes.txt")]) }
        EventLog { id: output }
    }
}

06 open · addFiles · removeFile · clear · getFiles

Every file passes metadata/readability and accept/maxFiles/maxSize checks before entering files/formValue.

Qt capture · image
open · addFiles · removeFile · clear · getFiles · Default
QML code
import QtQuick
import Syidax.Core

Item {
    component EventLog: Rectangle {
        id: logBox
        property var lines: []
        implicitWidth: 440
        implicitHeight: Math.max(56, logText.implicitHeight + 24)
        radius: 4; color: Theme.semantic.backgroundBold
        function log(value) { lines = lines.slice(-3).concat([String(value)]) }
        Text {
            id: logText; x: 12; y: 12; width: parent.width - 24
            text: logBox.lines.length ? logBox.lines.join("\n") : "Interact with the controls to see event results."
            wrapMode: Text.Wrap; color: Theme.semantic.textDefault
            font.family: Theme.monoFontFamily; font.pixelSize: 12
        }
    }

    implicitWidth: Math.max(360, childrenRect.width)
    implicitHeight: childrenRect.height
    // Focused Gallery routes: keep only the selected API and its prerequisites.
    id: featureStory

    readonly property alias uploadControl: upload

    Column {
        spacing: 12
        SyFileUpload { id: upload; width: 420; multiple: true; required: true }
        Flow {
            width: 560
            spacing: 8
            SyButton { visible: true; text: "upload.open()"; variant: "secondary"; size: "small"; onClicked: upload.open() }
            SyButton { id: exampleControl1;  visible: true; text: "upload.addFiles([\"file:///samples/report.pdf\"])"; variant: "secondary"; size: "small"; onClicked: upload.addFiles([Qt.resolvedUrl("../pdf-viewer/docs/quarterly-report.pdf")]) }
            SyButton { id: exampleControl2;  visible: true; text: "upload.removeFile(upload.getFile(0).id)"; variant: "secondary"; size: "small"; enabled: upload.files.length > 0; onClicked: upload.removeFile(upload.getFile(0).id) }
            SyButton { visible: true; text: "upload.getFiles()"; variant: "secondary"; size: "small"; onClicked: output.log("getFiles() → " + JSON.stringify(upload.getFiles().map((f) => f.name))) }
            SyButton { visible: true; text: "upload.clear()"; variant: "secondary"; size: "small"; onClicked: upload.clear() }
            SyButton { visible: true; text: "upload.reportValidity()"; variant: "secondary"; size: "small"; onClicked: output.log("reportValidity() → " + upload.reportValidity()) }
        }
        EventLog { id: output }
    }
}

07 Required field and reset

Compare the untouched field, validation error and reset result.

Qt capture · image
Required field and reset · Untouched
QML code
import QtQuick
import Syidax.Core

Column {
    width: 440; spacing: 12
    SyFileUpload {
        id: field; width: parent.width; name: "selection"; required: true
        label: "Required selection"; accept: ".pdf,.png"
        
        error: "Complete this field before continuing."
    }
    Row {
        spacing: 8
        SyButton { id: check; text: "Validate"; onClicked: field.reportValidity() }
        SyButton { id: reset; text: "Reset"; onClicked: field.reset() }
    }
    Text { text: "Status: " + (field.invalid ? "Invalid" : "Ready"); color: Theme.semantic.textDefault }
}

08 Custom error

Show an application-specific error, then reset the field.

Qt capture · image
Custom error · Untouched
QML code
import QtQuick
import Syidax.Core

Column {
    width: 440; spacing: 12
    SyFileUpload {
        id: field; width: parent.width; name: "selection"; required: true
        label: "Required selection"; accept: ".pdf,.png"
        
        error: "This selection is currently unavailable. Choose another."
    }
    Row {
        spacing: 8
        SyButton { id: check; text: "Show error"; onClicked: field.setCustomError() }
        SyButton { id: reset; text: "Reset"; onClicked: field.reset() }
    }
    Text { text: "Status: " + (field.invalid ? "Invalid" : "Ready"); color: Theme.semantic.textDefault }
}

API 80

Hover or select an item for its QML example and copy button.

Properties

PropertyTypeDefaultDescription
list[]The queue: [{ id, name, size, type, progress, status, message, url }]. Read it; change it through the methods.
string""Comma-separated rules: .ext · type/* · type/subtype. Empty = everything.
boolfalseSeveral files; otherwise a new pick replaces the queue.
real0Max size in bytes, checked once the size is read; 0 = no limit.
int0Max number of files in the queue; 0 = no limit.
string"Browse files"The Browse button (also the dialog title).
string"Drop files here or browse"The text in the zone.
string""Replaces the derived limits line under the drag text.
boolfalseNo list under the zone.
string"""" / default · error · warning · success — paints the border; set by the app.
string""The id of the clicked row (setSelectedFile()).
bool (read-only)—Files are being dragged over the zone.
string (read-only)—While dragging: all · some · none of the files pass accept; "" otherwise.
string (read-only)—The helper line actually shown.
list (read-only)—What the form submits: files.
bool (read-only)—The zone has keyboard focus
bool (read-only)—the pointer is over it.
string""The frame's title, helper text under the list, custom error copy.
string""The frame's title, helper text under the list, custom error copy.
string""The frame's title, helper text under the list, custom error copy.
string"files"The key in SyForm.values().
boolfalseEverything inert and always valid
boolfalsean empty queue is valueMissing.
read-only—Validity: "" · valueMissing · custom; invalid once touched or reported.
read-only—Validity: "" · valueMissing · custom; invalid once touched or reported.
read-only—Validity: "" · valueMissing · custom; invalid once touched or reported.
real360The width when the app sets none.

Methods

MethodDescription
Opens the file dialog (filtered by accept, multi-select with multiple). Ignored while disabled.
Every file passes metadata/readability and accept/maxFiles/maxSize checks before entering files/formValue. selected never fires before validation. A rejected file emits rejected({file,reason}) only. validating exposes pending work. Overlapping batches commit in order; clear discards late results. Successful single replacement includes the previous file in changed.removed (no separate removed signal); failure preserves it.
Removes one item; emits removed then changed. Unknown ids are ignored.
Empties the queue; emits changed with every item in removed (no removed signal).
Updates one item's bar (0 … 100) and status (given, or derived from the progress); emits changed.
A copy of the queue.
The item at index (or null); without an index the selected item, else the first, else null.
Highlights that row (what a click does).
Keyboard focus on / off the zone.
Keyboard focus on / off the zone.
Whether the status is "", silently
the same and reveals the error line.
"", "valueMissing" or "custom".
Forces the custom status
clears it.
clear() and the touched / submitted / custom flags off.

Signals

SignalArgumentsWhen
detail: { files }Files were added (dialog, drop, addFiles()); files holds the new items only.
detail: { file, reason }Validation failed; never added to files.
detail: { files, added, removed }Accepted additions/replacement/removal/clear/progress. Rejection alone does not emit it.
detail: { file, files }One file left through its × or removeFile(id). Not for clear().
detail: { files, isValid, status }The zone gained / lost keyboard focus; blur marks the field touched.
detail: { files, isValid, status }The zone gained / lost keyboard focus; blur marks the field touched.

Behavior

ItemBehavior / conditions
Every file passes metadata/readability and accept/maxFiles/maxSize checks before entering files/formValue. selected never fires before validation. A rejected file emits rejected({file,reason}) only. validating exposes pending work. Overlapping batches commit in order; clear discards late results. Successful single replacement includes the previous file in changed.removed (no separate removed signal); failure preserves it.
Every file passes metadata/readability and accept/maxFiles/maxSize checks before entering files/formValue. selected never fires before validation. A rejected file emits rejected({file,reason}) only. validating exposes pending work. Overlapping batches commit in order; clear discards late results. Successful single replacement includes the previous file in changed.removed (no separate removed signal); failure preserves it.
Files are being dragged over the zone.
While dragging: all · some · none of the files pass accept; "" otherwise.
Comma-separated rules: .ext · type/* · type/subtype. Empty = everything.
Files were added (dialog, drop, addFiles()); files holds the new items only.
Updates one item's bar (0 … 100) and status (given, or derived from the progress); emits changed.
selected({ files }) carries only the new items; for each, call setProgress(id, progress, status) as the upload advances: progress is clamped to 0 … 100; status may be "ready", "uploading", "success" or "error", and when omitted it follows the progress (100 → "success", > 0 → "uploading", 0 → unchanged). Rows show the bar once the status leaves "ready", colored by status; message on an item is shown under the name when present.
selected({ files }) carries only the new items; for each, call setProgress(id, progress, status) as the upload advances: progress is clamped to 0 … 100; status may be "ready", "uploading", "success" or "error", and when omitted it follows the progress (100 → "success", > 0 → "uploading", 0 → unchanged). fieldState paints the border — error · warning · success — to report the app's outcome; the component never sets it.
Rows show the bar once the status leaves "ready", colored by status; message on an item is shown under the name when present. The label sits above, message or the error line below the list.
Accepted additions/replacement/removal/clear/progress. Rejection alone does not emit it.
Removes one item; emits removed then changed. Unknown ids are ignored.
The id of the clicked row (setSelectedFile()).
The item at index (or null); without an index the selected item, else the first, else null.
No list under the zone.
The queue: [{ id, name, size, type, progress, status, message, url }]. Read it; change it through the methods.
disabled dims the zone and the rows, blocks click, drop, focus and keys, and ignores open() / addFiles(); the status is then always "".
Opens the file dialog (filtered by accept, multi-select with multiple). Ignored while disabled.
Every file passes metadata/readability and accept/maxFiles/maxSize checks before entering files/formValue. selected never fires before validation. A rejected file emits rejected({file,reason}) only. validating exposes pending work. Overlapping batches commit in order; clear discards late results. Successful single replacement includes the previous file in changed.removed (no separate removed signal); failure preserves it.
"" / default · error · warning · success — paints the border; set by the app.
required and an empty queue → valueMissing; setCustomError() → custom; disabled → "".
required and an empty queue → valueMissing; setCustomError() → custom; disabled → "".
The error line shows once the field is touched (a file was added or the zone lost focus) or after reportValidity().
The key in SyForm.values().
name is "files" by default; SyForm.values() receives the queue (formValue), i.e. the list of file items with their urls.
What the form submits: files.
The width when the app sets none.
the zone is at least 96 px tall and as wide as the field: preferredWidth 360 unless the app sets width.
The label sits above, message or the error line below the list.

Validation and event order

ItemBehavior / conditions
Only files passing metadata/readability/type/count/size checks enter files/formValue and emit selected. Pending files stay private while validating. Overlapping batches commit in call order; clear discards late results. hideFiles never bypasses validation. Valid single replacement includes the old file in changed.removed; rejected replacement preserves it. MIME/accept checks use extensions, not content inspection.
For hosts without Qt.labs.folderlistmodel, register Theme.fileMetadataProvider with fileSize(url) returning readable bytes or -1. The packaged C++ and Python hosts include this adapter.