How to create a qml pyside6 calender widget? - qt

0Based on https://stackoverflow.com/a/72465868/10985257 I wanted to create Widget, which allows me to click on a calender MonthGrid and read the clicked date, as a bonus the clicked date should be highlighted. My lazy attempt was following:
import QtQuick
import QtQuick.Layouts
import QtQuick.Window
ApplicationWindow {
id: mainFrame
width: 160
height: 160
visible: true
title: qsTr("CAL TEST")
Rectangle {
id: test
}
ColumnLayout {
DayOfWeekRow {
locale: grid.locale
Layout.fillWidth: true
}
MonthGrid {
id: grid
month: Calendar.December // this is how the Calendar can be used
year: 2022
locale: Qt.locale("de_DE")
Layout.fillWidth: true
onClicked: {
console.log("TEST")
}
}
}
}
So far it seems to work, but I struggle with the date. From the Documentation I've red that the signal Clicked has a parameter of the type date with the name date, but I don't know how to access the parameter.
Maybe my approach is to lazy, and I need a complete different approach?

In the following code snippet, I refactored a DatePicker component which is made up of MonthGrid and DayOfWeekRow. The component reacts to the MonthGrid.onClicked event and saves a copy of the date clicked. A custom delegate is provided so that we can highlight the selected date in a reddish-pink border.
import QtQuick
import QtQuick.Controls
Page {
background: Rectangle { color: "#8ac" }
Frame {
anchors.centerIn: parent
background: Rectangle {
color: "#ffe"
border.color: "grey"
radius: 5
}
MyDatePicker {
id: datePicker
month: Calendar.December
year: 2022
locale: Qt.locale("de_DE")
}
}
footer: Frame {
background: Rectangle {
color: "#ffe"
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: datePicker.selectedDate
}
}
}
// MyDatePicker.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ColumnLayout {
id: datePicker
property var selectedTime: 0
property date selectedDate: new Date(selectedTime)
property alias month: grid.month
property alias year: grid.year
property alias locale: grid.locale
DayOfWeekRow {
locale: grid.locale
Layout.fillWidth: true
}
MonthGrid {
id: grid
month: Calendar.December
year: 2022
locale: Qt.locale("de_DE")
Layout.fillWidth: true
delegate: Text {
property MonthGrid control: grid
property bool isCurrentItem: model.date.getTime() === selectedTime
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
opacity: model.month === control.month ? 1 : 0.5
text: day
font: control.font
Rectangle {
anchors.fill: parent
anchors.margins: -4
radius: 4
visible: isCurrentItem
color: "#fcc"
z: -2
}
}
onClicked: function (date) {
selectedTime = date.getTime();
}
}
}
You can Try it Online!

The QML signal documentation states the following:
Signals might have parameters. To access those, you should assign a
function to the handler. Both arrow functions and anonymous functions
work.
So you can either use
onClicked: (date) => { ... }
or
onClicked: function (date) { ... }
The QML Date documentation is stating the following:
The QML Date object extends the JS Date object with locale aware
functions.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Window {
id: root
width: 800
height: 600
visible: true
ColumnLayout {
anchors.centerIn: parent
DayOfWeekRow {
locale: grid.locale
Layout.fillWidth: true
}
MonthGrid {
id: grid
month: Calendar.December // this is how the Calendar can be used
year: 2022
locale: Qt.locale("de_DE")
Layout.fillWidth: true
onClicked: function (date) {
console.log("TEST", date)
console.log("date", date.getDate())
console.log("day", date.getDay())
console.log("month", date.getMonth())
console.log("year", date.getFullYear())
}
}
}
}

Related

Qt Select At Most 1 Marker on Map

In my code every marker that I clicked are selected(turn into green from red). I want just 1 can change. When I click another marker the marker I clicked before turns red again. Or When I click an empty area the marker I clicked before turns red again.
In qml my Item's code:
Component {
id: hazardous_img
MapQuickItem {
id: hazardousitem
anchorPoint.x: image.width/4
anchorPoint.y: image.height
coordinate: position
property bool isClicked: false
MouseArea {
anchors.fill: parent
onDoubleClicked: {
mainwindow.hazardousIconClicked(mapview.toCoordinate(Qt.point(mouse.x,mouse.y)))
}
onClicked: {
if (isClicked === false) {
image.source = "qrc:/grn-pushpin.png"
isClicked = true
} else {
image.source = "qrc:/red-pushpin.png"
isClicked = false
}
}
}
sourceItem: Image {
id: image
source: "qrc:/red-pushpin.png"
}
}
}
In QML this is usually done with using a ButtonGroup, but as you're not using AbstractButtons you need to write it yourself. Here is my solution for it.
I've used the ListModel to not only store the coordinates of each marker, but also a selected flag which is set to false by default. In the delegate I'm using the selected data role to show if a marker is selected or not.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtLocation 5.15
import QtPositioning 5.15
ApplicationWindow {
id: window
width: 640
height: 480
visible: true
title: qsTr("Map")
ListModel { id: markerModel }
Plugin {
id: mapPlugin
name: "osm"
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MouseArea {
anchors.fill: parent
onDoubleClicked: {
var coordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y))
var jsonObject = JSON.parse(JSON.stringify(coordinate))
jsonObject["selected"] = false
markerModel.append(jsonObject)
}
onClicked: map.deselectAll()
}
MapItemView {
model: markerModel
delegate: markerDelegate
}
function deselectAll() {
for (var i = 0; i < markerModel.count; ++i)
markerModel.setProperty(i, "selected", false)
}
Component {
id: markerDelegate
MapQuickItem {
id: markerItem
required property int index
required property real latitude
required property real longitude
required property bool selected
anchorPoint.x: waypointMarker.width / 2
anchorPoint.y: waypointMarker.height / 2
coordinate: QtPositioning.coordinate(latitude, longitude)
sourceItem: Rectangle {
id: waypointMarker
width: 20
height: 20
radius: 20
border.width: 1
border.color: mouseArea.containsMouse ? "red" : "black"
color: markerItem.selected ? "red" : "gray"
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: {
map.deselectAll()
markerModel.setProperty(markerItem.index, "selected", true)
}
}
}
}
}
}
I came up with yet another solution without looping over all items in the model. It just stores the index of the selected marker in a dedicated property. This has the drawback that if the model order changes the index can become invalid, also potential multi selection is hard to handle, but on the other hand it is faster because it doesn't need to iterate over all items.
I experimented a lot with DelegateModel, it seems to be a perfect match if one could use it in combination with MapItemView, because of the groups and the attached properties like inGroupName.
After that I've tried ItemSelectionModel, but it seems it is only intended to be used in combination with a view, e.g. TreeView. I couldn't find out how to generate a QModelIndex in QML without a TreeView.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtLocation 5.15
import QtPositioning 5.15
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
title: qsTr("Map")
property int selectedMarker: -1
Map {
id: map
anchors.fill: parent
plugin: Plugin {
id: mapPlugin
name: "osm"
}
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MouseArea {
anchors.fill: parent
onDoubleClicked: {
var coordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y))
markerModel.append(JSON.parse(JSON.stringify(coordinate)))
}
onClicked: root.selectedMarker = -1
}
MapItemView {
model: ListModel { id: markerModel }
delegate: markerDelegate
}
Component {
id: markerDelegate
MapQuickItem {
id: markerItem
required property int index
required property real latitude
required property real longitude
anchorPoint.x: waypointMarker.width / 2
anchorPoint.y: waypointMarker.height / 2
coordinate: QtPositioning.coordinate(latitude, longitude)
sourceItem: Rectangle {
id: waypointMarker
width: 20
height: 20
radius: 20
border.width: 1
border.color: mouseArea.containsMouse ? "red" : "black"
color: markerItem.index === root.selectedMarker ? "red" : "gray"
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: root.selectedMarker = markerItem.index
}
}
}
}
}
I promise this is the last answer on that question.
This one is using an ItemSelectionModel and a few undocumented functions, e.g. ListModel.index(row, col).
itemSelectionModel.hasSelection is used in the color binding to trigger a reevaluation in order to call isRowSelected and set the color accordingly whenever the selection has changed.
If the user clicks on the background the clear() is called to clear the selection.
I think out of the three this is the best solution. It can be easily upgraded to allow multi selection as shown below. Also the ItemSelectionModel can be used by other views to show the data and selection.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtLocation 5.15
import QtPositioning 5.15
import QtQml.Models 2.15
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
title: qsTr("Map")
Map {
id: map
anchors.fill: parent
plugin: Plugin {
id: mapPlugin
name: "osm"
}
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MouseArea {
anchors.fill: parent
onDoubleClicked: function(mouse) {
markerModel.append(map.toCoordinate(Qt.point(mouse.x, mouse.y)))
}
onClicked: itemSelectionModel.clear()
}
MapItemView {
model: ListModel { id: markerModel }
delegate: markerDelegate
}
ItemSelectionModel {
id: itemSelectionModel
model: markerModel
}
Component {
id: markerDelegate
MapQuickItem {
id: markerItem
required property int index
required property real latitude
required property real longitude
anchorPoint.x: waypointMarker.width / 2
anchorPoint.y: waypointMarker.height / 2
coordinate: QtPositioning.coordinate(latitude, longitude)
sourceItem: Rectangle {
id: waypointMarker
width: 20
height: 20
radius: 20
border.width: 1
border.color: mouseArea.containsMouse ? "red" : "black"
color: {
itemSelectionModel.hasSelection
return itemSelectionModel.isRowSelected(markerItem.index) ? "red" : "gray"
}
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: itemSelectionModel.select(markerModel.index(markerItem.index, 0),
ItemSelectionModel./*ClearAnd*/Select)
}
}
}
}
}

Can't bind on current item in QML view/model

I have a simple task: need to output some information about current item of ListView separately. For example, in outside Label, but I can't succeed.
I figured out that problem related to where model was defined. If it is separate object with id this behavior happened. But when I define it inlined inside the view, all works fine. This behaviour is very weird and looks like a bug.
To make this code work, just switch to commented model definition. Anybody encounter this problem or may be know something?
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import Qt.labs.calendar 1.0
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true
CalendarModel {
id: myModel
from: new Date(2018, 0, 1)
to: new Date(2018, 11, 31)
}
ColumnLayout {
anchors.fill: parent
Label {
id: myLabel
text: "myLabel1"
}
Label {
text: myModel.monthAt(myView.currentIndex)
}
ListView {
id: myView
Layout.fillWidth: true
Layout.fillHeight: true
snapMode: ListView.SnapOneItem
orientation: ListView.Horizontal
highlightRangeMode: ListView.StrictlyEnforceRange
boundsBehavior: Flickable.StopAtBounds
model: myModel
// model: CalendarModel {
// id: myModel
// from: new Date(2018, 0, 1)
// to: new Date(2018, 11, 31)
// }
delegate: Rectangle {
color: "green"
width: myView.width
height: 200
Text {
anchors.centerIn: parent
text: model.month
}
}
onCurrentIndexChanged: {
console.log(currentIndex)
myLabel.text = String(currentIndex)
}
ScrollIndicator.horizontal: ScrollIndicator {}
}
}
}

How to set an item property of one qml from main.qml

I waste my time to find how set the visible property to false, the delegate being in an another qml file.
For instance here is a simple example based on Places Map.
Marker.qml
import QtQuick 2.0
import QtLocation 5.6
MapQuickItem {
id: idPointsMarker
sourceItem: Loader{sourceComponent: idRect}
visible: true //if set manually to false, everything works correctly
Component{
id: idRect
Rectangle{
width: 20
height: 20
color: "blue"
}
}
}
and the main.qml
import QtQuick 2.0
import QtQuick.Window 2.0
import QtLocation 5.6
import QtPositioning 5.6
Window {
width: 512
height: 512
visible: true
PositionSource {
...
}
property variant locationOslo: QtPositioning.coordinate( 59.93, 10.76)
PlaceSearchModel {
...
}
Map {
id: map
anchors.fill: parent
plugin: Plugin {name: "osm"}
center: locationOslo
zoomLevel: 13
MouseArea {
id : mouseMap
anchors.fill: parent
onDoubleClicked: {
console.log("DoubleClicked")
Marker.idPointsMarker.visible = false // pb is here
}
}
MapItemView {
model: searchModel
delegate: Marker{
coordinate: place.location.coordinate
}
}
}
}
I wish to toggle the visibility to false on doubleclick. I am not able to access the property anyhow the way i write it. What is the correct syntax?
Sorry for a so simple question. Thanks for help.
You do not have to set the property in main.qml, you must do it in Marker.qml, since the elements of Marker.qml can access all the elements of main.qml. One solution is to establish a property of type bool that manages the visibility and that changes in the double click:
main.qml
Map {
[...]
property bool isVisibleItems: true
MouseArea {
id : mouseMap
anchors.fill: parent
onDoubleClicked: map.isVisibleItems = !map.isVisibleItems
}
[...]
Marker.qml
import QtQuick 2.0
import QtLocation 5.6
MapQuickItem {
id: idPointsMarker
sourceItem: Loader{sourceComponent: idRect}
visible: map.isVisibleItems
Component{
id: idRect
Rectangle{
width: 20
height: 20
color: "blue"
}
}
}
In the following link there is an example

Make effective property alias, using Connections, Binding or Qt.binding

Is it possible to make mutual connection (without loop issue), using Connections, Binding or Qt.binding()?
It is possible to connect, say, SwipeView.currentIndex to TabBar.currentIndex and vise versa. When I page through SwipeView, then current tab of TabBar is also changed and vice versa. There is no binding loop of properties.
How to achieve this in Repeater? When some item created by Repeater became selected (in some sense), then I want to rebind its properties to another standalone item, which operates like editor of the selected item. The state of elements in selected item should depend on the state of items into the editor. But on selection changing I need to initialize items in editor using values from newly selected item.
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0
ApplicationWindow {
id: root
property var currentDayOfWeek: { "enabled": false, "time": "08:00" }
visible: true
SystemPalette {
id: palette
}
RowLayout {
anchors.centerIn: parent
Column {
ButtonGroup { id: weekButtonGroup }
Repeater {
model: 7
RowLayout {
Settings {
id: dayOfWeekSettings
category: Qt.locale("C").standaloneDayName(index, Locale.LongFormat)
property bool enabled: false
property string time: "08:00"
}
Label {
text: dayOfWeekSettings.time
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
color: dayOfWeekSettings.enabled ? palette.highlight : palette.base
}
Layout.fillHeight: true
}
RadioButton {
text: Qt.locale().standaloneDayName(index, Locale.LongFormat)
onCheckedChanged: {
if (checked) {
root.currentDayOfWeek = dayOfWeekSettings
timeField.text = Qt.binding(function() { return dayOfWeekSettings.time }) // ???
enabledCheckBox.checked = Qt.binding(function() { return dayOfWeekSettings.enabled }) // ???
}
}
ButtonGroup.group: weekButtonGroup
Layout.fillHeight: true
}
}
}
Layout.fillHeight: true
}
Column {
// Editor
TextField {
id: timeField
text: currentDayOfWeek.time // ???
inputMask: "00:00;_"
inputMethodHints: Qt.ImhDigitsOnly
}
CheckBox {
id: enabledCheckBox
checked: currentDayOfWeek.enabled // ???
}
Layout.fillHeight: true
}
}
}
How to achieve this? Is there canonical way to do this? Above example is not the solution.
The following code works as I want:
import QtQuick 2.7
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0
ApplicationWindow {
id: root
//property var currentDayOfWeek: { "enabled": false, "time": "08:00" }
visible: true
SystemPalette {
id: palette
}
RowLayout {
anchors.centerIn: parent
Column {
ButtonGroup { id: weekButtonGroup }
Repeater {
model: 7
RowLayout {
Settings {
id: dayOfWeekSettings
category: Qt.locale("C").standaloneDayName(index, Locale.LongFormat)
property bool enabled: false
property string time: "08:00"
Binding on enabled {
when: dayOfWeekSettingsCheckBox.checked
value: enabledCheckBox.checked
}
Binding on time {
when: dayOfWeekSettingsCheckBox.checked
value: timeField.text
}
}
Label {
text: dayOfWeekSettings.time
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
color: dayOfWeekSettings.enabled ? palette.highlight : palette.base
}
Layout.fillHeight: true
}
RadioButton {
id: dayOfWeekSettingsCheckBox
text: Qt.locale().standaloneDayName(index, Locale.LongFormat)
onCheckedChanged: {
if (checked) {
//root.currentDayOfWeek = dayOfWeekSettings
timeField.text = dayOfWeekSettings.time
enabledCheckBox.checked = dayOfWeekSettings.enabled
}
}
ButtonGroup.group: weekButtonGroup
Layout.fillHeight: true
}
}
}
Layout.fillHeight: true
}
Column {
id: editor
TextField {
id: timeField
//text: currentDayOfWeek.time
inputMask: "00:00;_"
inputMethodHints: Qt.ImhDigitsOnly
}
CheckBox {
id: enabledCheckBox
//checked: currentDayOfWeek.enabled
}
Layout.fillHeight: true
}
}
}
but there is an issue: editor filled with values in onCheckedChanged when checked became true. But what if when: in Bindings shoot first? Is it possible? Should I use delayed?
If I comment out:
timeField.text = dayOfWeekSettings.time
enabledCheckBox.checked = dayOfWeekSettings.enabled
and uncomment all the commented in above solution, then also all works fine. But the suspicion still persist.

QML reset dialog with tabview

I was trying to implement a tabbed Dialog in QML with the means to reset it to the intial values.
Since tabs are dynamically instantiated, none of the straight forward methods seem to work. The parent Dialog can not reference the inner Combobox and the Combobox can not reference the outer Dialog. How can this be achieved?
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
Dialog {
id: dlg
title: "Settings"
visible: true
standardButtons: StandardButton.Apply | StandardButton.Reset
property string val: ""
onApply: console.log(val)
onReset: {
// RESET COMBOBOX TO DEFAULT
}
TabView {
id: tabView
anchors.fill: parent
Tab {
title: "ValueTab"
id: tabVal
GridLayout {
id: gridVal
anchors.fill: parent
GroupBox {
title: qsTr("Choose value")
id: gb
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
id: cl
ComboBox {
id: valueChooser
editable: false
model: ListModel {
id: listModel
ListElement { text: "One" }
ListElement { text: "Two" }
ListElement { text: "Three" }
}
Layout.fillWidth: true
onCurrentTextChanged : val = currentText
}
}
}
}
}
}
}
I am quite unsure, if I got your question right as you say, you can not reference the Dialog from within the Combobox. I can not see the reason why.
Assuming the example of yours contains indeed your problem and all you want to do is to reset the values (and you know the original values) once the reset button is pressed, this is how I would solve it.
Using the Connections-type to connect to the Dialog's reset() from within the Combobox
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
Dialog {
id: dlg
title: "Settings"
visible: true
standardButtons: StandardButton.Apply | StandardButton.Reset
property string val: ""
onApply: console.log(val)
onReset: {
// **DONT** RESET COMBOBOX TO DEFAULT **HERE**
}
TabView {
id: tabView
anchors.fill: parent
Tab {
title: "ValueTab"
id: tabVal
GridLayout {
id: gridVal
anchors.fill: parent
GroupBox {
title: qsTr("Choose value")
id: gb
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
id: cl
ComboBox {
id: valueChooser
editable: false
model: ListModel {
id: listModel
ListElement { text: "One" }
ListElement { text: "Two" }
ListElement { text: "Three" }
}
Layout.fillWidth: true
onCurrentTextChanged : val = currentText
/// *** INTERESTING PART HERE! ***
Connections {
target: dlg
onReset: {
// RESET COMBOBOX TO DEFAULT **HERE** INSTEAD
valueChooser.currentIndex = 0
}
}
}
}
}
}
}
}
}

Resources