Skip to main content
Passa al contenuto

Aggiungere una finestra

Imparare a conoscere i dialoghi di Kirigami.

Rendere utili i programmi

Abbiamo una finestra, abbiamo le schede e abbiamo le azioni. Dobbiamo ancora trovare un modo per inserire un nome, una descrizione e una data a nostra scelta.

Un modo potrebbe essere quello di creare una nuova pagina dove mettere gli elementi gli elementi per l'inserimento. Tuttavia utilizzare una pagina intera solo per inserire un nome, una descrizione e una data sembra un po' eccessivo.

Utilizzeremo invece una finestra di dialogo.

Finestra visualizzata al centro dell'applicazione

Apertura della finestra

pageStack.initialPage: Kirigami.ScrollablePage {
    // ...
    actions: [
        Kirigami.Action {
            id: addAction
            icon.name: "list-add"
            text: i18nc("@action:button", "Add kountdown")
            onTriggered: addDialog.open()
        }
    ]
}

Per prima cosa modifichiamo l'azione del tutorial precedente: solo un Kirigami.Action che attiva la funzione open() della finestra di dialogo.

Finestre di dialogo per l'aggiunta del conto alla rovescia

Il nuovo componente che aggiungiamo è un Kirigami.Dialog. Le finestre di dialogo vengono visualizzate al centro della finestra e possono essere utilizzate per fornire informazioni aggiuntive rilevanti per il contenuto corrente. Non possono essere spostati, ma adattano la propria dimensione alla finestra.

Kirigami.ApplicationWindow {
    // ...
    Kirigami.Dialog {
        id: addDialog
        title: i18nc("@title:window", "Add kountdown")
        standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
        padding: Kirigami.Units.largeSpacing
        preferredWidth: Kirigami.Units.gridUnit * 20

        // I layout dei moduli aiutano ad allineare e strutturare un layout con diversi input
        Kirigami.FormLayout {
            // I campi di testo ti consentono di inserire testo in una casella di testo sottile
            Controls.TextField {
                id: nameField
                // Fornisce un'etichetta allegata al campo di testo
                Kirigami.FormData.label: i18nc("@label:textbox", "Name*:")
                // Cosa fare dopo che l'input è stato accettato (cioè premuto Invio) In questo caso,
                // sposta lo stato attivo sul campo successivo
                onAccepted: descriptionField.forceActiveFocus()
            }
            Controls.TextField {
                id: descriptionField
                Kirigami.FormData.label: i18nc("@label:textbox", "Description:")
                placeholderText: i18n("Optional")
                // Ancora una volta, sposta lo stato attivo sul campo successivo
                onAccepted: dateField.forceActiveFocus()
            }
            Controls.TextField {
                id: dateField
                Kirigami.FormData.label: i18nc("@label:textbox", "ISO Date*:")
                // D significa un numero richiesto compreso tra 1 e 9, 9 significa un numero
                // richiesto compreso tra 0 e 9
                inputMask: "D999-99-99"
                // Qui confermiamo l'operazione proprio come fare clic sul pulsante OK
                onAccepted: addDialog.onAccepted()
            }
            Controls.Label {
                text: "* = required fields"
            }
        }
        // La logica del dialogo va qui
    }
    // ...
}

Le finestre di dialogo per impostazione predefinita hanno un header e un footer, entrambi ereditati da Controls.Dialog.

L'intestazione per impostazione predefinita include un titolo e un pulsante di chiusura che può essere disabilitato con showCloseButton. Il piè di pagina per impostazione predefinita include un pulsante di chiusura e può essere sovrascritto con standardButtons.

Per prima cosa lo impostiamo per mostrare un pulsante "Ok" e un pulsante "Annulla", aggiungiamo un po' di riempimento e aggiungiamo una preferredWidth ragionevole. La larghezza preferita è la dimensione prevista predefinita della finestra di dialogo, che può aumentare se necessario. Possiamo usare lo standard Kirigami.Units che rivisiteremo più avanti.

Poi arriviamo a un Kirigami.FormLayout. A differenza di un ColumnLayout, il layout dei suoi componenti figlio è automatico e centrato, con etichette opzionali. Come suggerisce il nome, viene utilizzato per creare moduli di input.

Questi layout di modulo sono progettati per funzionare con una varietà di tipi di input diversi, anche se ci limitiamo ai semplici input Controls.Textfield che ci forniscono semplici caselle di testo in cui scrivere cose.

Abbiamo creato elementi Textfield che agiscono come:

  1. Ingresso per il nome del nostro conto alla rovescia
  2. Ingresso per la descrizione del nostro conto alla rovescia
  3. Ingresso per le date che stiamo aspettando, che devono essere fornite nel formato YYYY-MM-DD

All'interno di ciascuno di questi elementi Controls.Textfield, stiamo impostando una proprietà Kirigami.FormData.label che ci consente di definire le etichette per essi. Il modulo presenterà le etichette corrette a sinistra di ciascuno di questi campi di immissione testo.

Infine, stiamo anche impostando la proprietà onAccepted per attivare il metodo forceActiveFocus() del campo seguente; questo cambierà il campo attivo una volta che l'utente preme il tasto INVIO, migliorando l'usabilità del modulo.

Abbiamo anche impostato una proprietà chiamata inputMask sul campo di testo per la nostra data. Impostandolo su "D999-99-99" impedisce agli utenti di inserire qualcosa che potrebbe interrompere la funzionalità dell'applicazione (come il testo), limitandoli a inserire solo cifre che possiamo quindi provare ad analizzare in un oggetto data.

Una volta completata l'interfaccia utente per la finestra di dialogo, dobbiamo modificare il modo in cui si comporta. Per questo abbiamo bisogno di tre cose:

  1. Mostra il pulsante Ok solo quando i campi obbligatori sono compilati
  2. Aggiungere le informazioni di input al modello
  3. Cancella il modulo di input
Kirigami.Dialog {
    // ... Una volta inizializzato Kirigami.Dialog, vogliamo creare un'associazione personalizzata
    // per rendere visibile il pulsante Ok solo se i campi di testo richiesti sono compilati. Per
    // questo usiamo Kirigami.Dialog.standardButton(pulsante):
    Component.onCompleted: {
        const button = standardButton(Kirigami.Dialog.Ok);
        // () => è una funzione freccia JavaScript
        button.enabled = Qt.binding( () => requiredFieldsFilled() );
    }
    onAccepted: {
        // L'associazione viene creata, ma dobbiamo ancora renderla non cliccabile a meno che i
        // campi non siano compilati
        if (!addDialog.requiredFieldsFilled()) return;
        appendDataToModel();
        clearFieldsAndClose();
    }
}

La prima cosa da fare è creare un legame tra la proprietà enabled del pulsante OK e un controllo se i campi sono compilati, che in questo caso deve essere fatto con Qt.binding() in JavaScript. In effetti la riga:

button.enabled = Qt.binding( () => requiredFieldsFilled() );

è simile ai collegamenti QML che abbiamo visto finora, come nel seguente pseudo-codice:

enabled: requiredFieldsFilled()

Il gestore del segnale che attiva il pulsante Ok è onAccepted. Rimane vuoto e senza fare nulla se i campi obbligatori sono compilati; in caso contrario, aggiungerà l'input al modello e cancellerà la finestra di dialogo per la prossima volta che verrà aperta.

Kirigami.Dialog {
    // ... Controlliamo che il nameField non sia vuoto e che il dateField (che ha una inputMask) sia
    // completamente riempito
    function requiredFieldsFilled() {
        return (nameField.text !== "" && dateField.acceptableInput);
    }
    function appendDataToModel() {
        kountdownModel.append({
            name: nameField.text,
            description: descriptionField.text,
            date: new Date(dateField.text)
        });
    }
    function clearFieldsAndClose() {
        nameField.text = ""
        descriptionField.text = ""
        dateField.text = ""
        addDialog.close();
    }
}

Per il campo del nome richiesto, tutto ciò che dobbiamo fare è verificare se il testo del campo è una stringa vuota. Per il campo della data, poiché ha una maschera di input, dobbiamo invece utilizzare acceptableInput, che diventa vero solo quando l'intero campo è riempito e contiene solo caratteri accettabili.

Quindi, il metodo append() del nostro modello di elenco kountdownModel aggiunge un oggetto JavaScript che include le proprietà che abbiamo fornito.

Infine, ci assicuriamo di cancellare i campi di testo impostando le loro proprietà text su una stringa vuota, quindi close().

Una volta salvati i nostri file e creato il nostro programma, saremo in grado di aggiungere i nostri conti alla rovescia personalizzati! Possiamo fare un ultimo tocco per migliorare l'interfaccia, ovvero rimuovere il conto alla rovescia fittizio che avevamo nelle lezioni precedenti:

26
27
28
    ListModel {
        id: kountdownModel
    }

In secondo luogo, ora che abbiamo una data effettiva con cui giocare, possiamo calcolare il tempo fino a tale data:

47
48
49
50
                    Kirigami.Heading {
                        level: 1
                        text: i18n("%1 days", Math.round((date-Date.now())/86400000))
                    }

E in terzo luogo aumenta la dimensione della finestra in modo da avere più spazio per le nostre nuove carte:

 9
10
    width: 600
    height: 400

Molto più bello.

Il nostro programma finora

Main.qml:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as Controls
import org.kde.kirigami as Kirigami

Kirigami.ApplicationWindow {
    id: root

    width: 600
    height: 400

    title: i18nc("@title:window", "Day Kountdown")

    globalDrawer: Kirigami.GlobalDrawer {
        isMenu: true
        actions: [
            Kirigami.Action {
                text: i18n("Quit")
                icon.name: "application-exit-symbolic"
                shortcut: StandardKey.Quit
                onTriggered: Qt.quit()
            }
        ]
    }

    ListModel {
        id: kountdownModel
    }

    Component {
        id: kountdownDelegate
        Kirigami.AbstractCard {
            contentItem: Item {
                implicitWidth: delegateLayout.implicitWidth
                implicitHeight: delegateLayout.implicitHeight
                GridLayout {
                    id: delegateLayout
                    anchors {
                        left: parent.left
                        top: parent.top
                        right: parent.right
                    }
                    rowSpacing: Kirigami.Units.largeSpacing
                    columnSpacing: Kirigami.Units.largeSpacing
                    columns: root.wideScreen ? 4 : 2

                    Kirigami.Heading {
                        level: 1
                        text: i18n("%1 days", Math.round((date-Date.now())/86400000))
                    }

                    ColumnLayout {
                        Kirigami.Heading {
                            Layout.fillWidth: true
                            level: 2
                            text: name
                        }
                        Kirigami.Separator {
                            Layout.fillWidth: true
                            visible: description.length > 0
                        }
                        Controls.Label {
                            Layout.fillWidth: true
                            wrapMode: Text.WordWrap
                            text: description
                            visible: description.length > 0
                        }
                    }
                    Controls.Button {
                        Layout.alignment: Qt.AlignRight
                        Layout.columnSpan: 2
                        text: i18n("Edit")
                    }
                }
            }
        }
    }

    Kirigami.Dialog {
        id: addDialog
        title: i18nc("@title:window", "Add kountdown")
        standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
        padding: Kirigami.Units.largeSpacing
        preferredWidth: Kirigami.Units.gridUnit * 20

        // Form layouts help align and structure a layout with several inputs
        Kirigami.FormLayout {
            // Textfields let you input text in a thin textbox
            Controls.TextField {
                id: nameField
                // Provides a label attached to the textfield
                Kirigami.FormData.label: i18nc("@label:textbox", "Name*:")
                // What to do after input is accepted (i.e. pressed Enter)
                // In this case, it moves the focus to the next field
                onAccepted: descriptionField.forceActiveFocus()
            }
            Controls.TextField {
                id: descriptionField
                Kirigami.FormData.label: i18nc("@label:textbox", "Description:")
                placeholderText: i18n("Optional")
                // Again, it moves the focus to the next field
                onAccepted: dateField.forceActiveFocus()
            }
            Controls.TextField {
                id: dateField
                Kirigami.FormData.label: i18nc("@label:textbox", "ISO Date*:")
                // D means a required number between 1-9,
                // 9 means a required number between 0-9
                inputMask: "D999-99-99"
                // Here we confirm the operation just like
                // clicking the OK button
                onAccepted: addDialog.onAccepted()
            }
            Controls.Label {
                text: "* = required fields"
            }
        }
        // Once the Kirigami.Dialog is initialized,
        // we want to create a custom binding to only
        // make the Ok button visible if the required
        // text fields are filled.
        // For this we use Kirigami.Dialog.standardButton(button):
        Component.onCompleted: {
            const button = standardButton(Kirigami.Dialog.Ok);
            // () => is a JavaScript arrow function
            button.enabled = Qt.binding( () => requiredFieldsFilled() );
        }
        onAccepted: {
            // The binding is created, but we still need to make it
            // unclickable unless the fields are filled
            if (!addDialog.requiredFieldsFilled()) return;
            appendDataToModel();
            clearFieldsAndClose();
        }
        // We check that the nameField is not empty and that the
        // dateField (which has an inputMask) is completely filled
        function requiredFieldsFilled() {
            return (nameField.text !== "" && dateField.acceptableInput);
        }
        function appendDataToModel() {
            kountdownModel.append({
                name: nameField.text,
                description: descriptionField.text,
                date: new Date(dateField.text)
            });
        }
        function clearFieldsAndClose() {
            nameField.text = ""
            descriptionField.text = ""
            dateField.text = ""
            addDialog.close();
        }
    }

    pageStack.initialPage: Kirigami.ScrollablePage {
        title: i18nc("@title", "Kountdown")

        // Kirigami.Action encapsulates a UI action. Inherits from Controls.Action
        actions: [
            Kirigami.Action {
                id: addAction
                // Name of icon associated with the action
                icon.name: "list-add-symbolic"
                // Action text, i18n function returns translated string
                text: i18nc("@action:button", "Add kountdown")
                // What to do when triggering the action
                onTriggered: addDialog.open()
            }
        ]

        Kirigami.CardsListView {
            id: cardsView
            model: kountdownModel
            delegate: kountdownDelegate
        }
    }
}

![Schermata dell'applicazione con quattro schede di esempio](aggiunto Kountdowns.webp)