I'm trying to find a means of creating dynamic chartviews using QML. The general idea is for a user to click an element on a ComboBox and click a button to either add a chartview to the screen or remove it.
Currently I'm able to display my charts statically in a RowLayout like so:
import QtQuick 2.15
import QtCharts 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.12
Item{ property int firstModel : 1
property int secondModel : 2
property int maxVal : 8
property int minVal: -8
RowLayout {
anchors.fill: parent
spacing: 10
ChartView{
id: firstModelChartView
animationOptions: ChartView.NoAnimation
theme: ChartView.ChartThemeLight
antialiasing: true
Layout.fillHeight: true
Layout.fillWidth: true
DateTimeAxis{
id:axisXValues
min:new Date(SensorManager.audioStream[firstModel].xMin)
max:new Date(SensorManager.audioStream[firstModel].xMax)
format: "hh:mm:ss:zzz"
}
ValueAxis {
id: axisYValues
min: gLinkMin
max: gLinkMax
}
LineSeries {
id: innerChamberLineSeries
useOpenGL: true
name: "Inner Chamber"
axisX: axisXValues
axisY: axisYValues
}
VXYModelMapper {
id: innerChamberLineSeriesModelMapper
model: SensorManager.audioStream[firstModel]
series: innerChamberLineSeries
firstRow: 1
xColumn: 0
yColumn: 1
}
}
/// SEcond Model ...... Etc You get the gist
}
}
The model is a QAbstractModel Type from C++ and I'm able to display this in realtime. My problem is I've got 20 of these data streams and I don't want to show them all at once. so I want to give the user the ability to be able to add or remove multiple of these chartviews from the screen.
I'm trying to use a GridView and basically adapt this snippet from this post but I'm not really getting far with it.
My Idea is to initially create a chartview and then attach a lineseries to that chartview, here's what I've got so far but I'm stumped on how to attach a specific lineseries model to a chartview and how you remove a specific chartview from the Gridview.
import QtQuick 2.15
import QtCharts 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.12
Item{ property int firstModel : 1
property int secondModel : 2
property int maxVal : 8
property int minVal: -8
ListModel{
id: chartsModel
Component.onCompleted: {
chartsModel.append({"name": qsTr("Inner Vinration Chamber"), value: firstModel })
chartsModel.append({"name": qsTr("Extruder Vibration Y"), value: secondModel })
///////
}
}
ListModel{
id:chartViewModel
}
Row {
id: chartControlsRow
anchors.fill: parent
anchors{
leftMargin: 10
topMargin: 10
}
spacing:0
CustomComboBox {
id: chartSelector
model: chartsModel
textRole: "name"
onCurrentIndexChanged: {
var model = SensorManager.audioStream[chartsModel.get(currentIndex).value]
}
}
CustomButton
{
id: addChartButton
text: "Add Chart"
onClicked: {
modelId.append({'mColor': 'blue'})
var chartViewModel = Qt.createQmlObject('import QtQuick 2.14; ListModel { }',
Qt.application, 'dynamicChartViewModel');
}
}
CustomButton
{
id:removeChartButton
text: "Remove Chart"
onClicked: {
console.log(chartSelector.currentIndex)
chartViewModel.remove(chartSelector.currentIndex);
}
}
}
Rectangle{
anchors.fill: parent
anchors.topMargin: 55
color: "black"
GridView {
id: mGridViewId
anchors.fill: parent
cellWidth: 400; cellHeight: 400
model: chartViewModel
delegate: Rectangle {
width: mGridViewId.cellWidth;
height: mGridViewId.cellHeight
color: mColor
ChartView {
id: mChartView
width: parent.width;
height: parent.height
DateTimeAxis{
id:axisXValues
format: "hh:mm:ss:zzz"
}
ValuesAxis{
id:axisYValues
min:gLinkMin
max:gLinkMax
}
LineSeries {
name: "Lineseries"
id: mLineSeries
useOpenGL: true
axisX: axisXValues
axisY: axisYValues
}
VXYModelMapper{
id: modelMapper
series: mLineSeries
firstRow: 1
xColumn: 0
yColumn: 1
}
}
}
function getDelegateInstanceAt(index) {
return contentItem.children[index];
}
}
}
}
How do I Achieve this? Any tips or pointers would be helpful
Related
I have two questions which are somehow related.
I have created a route on an open street map and I want to extract a list of points that correspond to the way points of the generated route (not just the start and the finish). How can this be achieved? For example I want to extract way points for the generated red route from the image bellow (of course I do not want to extract all the points from a route but from 10 in 10 meters).
How do I erase the generated route with red, and have the original map (without the red route)
I have tried many function on the map item but non of them worked. For example I have tried the code below but the red route remains.
function clearMapDataForSession()
{
mapview.clearData();
routeModel.update()
}
You can get a list of coordinates from the Route by using the properties path or segments. The path property directly gives you a list of coordinates on the given Route, the segments property on the other hand gives you a list of RouteSegments which in turn contain a list of coordinates given by its path property.
Print the list of Route coordinates via segments:
var segments = routeModel.get(0).segments
for (var i = 0; i < segments.length; i++) {
var path = segments[i].path
for (var j = 0; j < path.length; j++)
console.log(path[j])
}
Print the list of Route coordinates via path:
var path = routeModel.get(0).path
for (var i = 0; i < path.length; i++) {
console.log(path[i])
}
If you compare the list of coordinates given by the two options, they are the same. The benefit of RouteSegments is that you get the distance of the segment as a property. So if you want to generate a list of coordinates/points on the Route at the same distance, this would help you in writing some sort of an algorithm.
In order to erase a generated Route you need to call reset() on the RouteModel. If you want to also clear the waypoints of a RouteQuery you should call clearWaypoints() as well.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtLocation 5.15
import QtPositioning 5.15
ApplicationWindow {
id: window
width: 800
height: 600
visible: true
title: qsTr("Map")
header: ToolBar {
RowLayout {
anchors.fill: parent
ToolButton {
text: qsTr("Reset")
onClicked: {
routeQuery.clearWaypoints()
routeModel.reset()
}
}
}
}
Plugin {
id: mapPlugin
name: "osm"
}
RouteQuery {
id: routeQuery
}
RouteModel {
id: routeModel
plugin: mapPlugin
query: routeQuery
autoUpdate: false
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MapItemView {
model: routeModel
delegate: MapRoute {
route: routeData
line.color: "blue"
line.width: 5
smooth: true
opacity: 0.8
}
}
MapItemView {
model: routeModel.status == RouteModel.Ready ? routeModel.get(0).path : null
delegate: MapQuickItem {
anchorPoint.x: pathMarker.width / 2
anchorPoint.y: pathMarker.height / 2
coordinate: modelData
sourceItem: Rectangle {
id: pathMarker
width: 8
height: 8
radius: 8
border.width: 1
border.color: "black"
color: "yellow"
}
}
}
MapItemView {
model: routeQuery.waypoints
delegate: MapQuickItem {
anchorPoint.x: waypointMarker.width / 2
anchorPoint.y: waypointMarker.height / 2
coordinate: modelData
sourceItem: Rectangle {
id: waypointMarker
width: 10
height: 10
radius: 10
border.width: 1
border.color: "black"
color: "red"
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
routeQuery.addWaypoint(map.toCoordinate(Qt.point(mouse.x,mouse.y)))
routeModel.update()
}
}
}
}
How I can use different delegates in qml for the ListView. For example I have QList<SomeObject*> list, SomeObject has two fields: type (circle, rectangle, etc), and someValue. I created QListModel for this list. I have different qml elements (circle.qml, rectangle.qml, etc). How can I view delegate for an item by type, and view field someValue in this delegate. And can I position these delegates without a table/list, I wanted to position them by coordinates(x, y).
To have different delegates based on a role (it can also be done depending on the row or column), you should use DelegateChooser with DelegateChoice:
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import Qt.labs.qmlmodels 1.0
Window {
width: 640
height: 480
visible: true
ListModel {
id: shapeModel
ListElement {
type: "circle"
value: 100
x: 10
y: 10
}
ListElement {
type: "rectangle"
value: 30
x: 100
y: 100
}
ListElement {
type: "circle"
value: 30
x: 300
y: 450
}
ListElement {
type: "rectangle"
value: 20
x: 500
y: 200
}
ListElement {
type: "circle"
value: 25
x: 650
y: 100
}
ListElement {
type: "rectangle"
value: 60
x: 600
y: 200
}
}
Flickable {
anchors.fill: parent
contentWidth: contentItem.childrenRect.width
contentHeight: contentItem.childrenRect.height
Repeater {
model: shapeModel
delegate: DelegateChooser {
role: "type"
DelegateChoice {
roleValue: "rectangle"
Rectangle {
x: model.x
y: model.y
height: model.value
width: model.value
border.color: "orange"
border.width: 1
}
}
DelegateChoice {
roleValue: "circle"
Rectangle {
x: model.x
y: model.y
height: model.value
width: model.value
radius: model.value/2
border.color: "blue"
border.width: 1
}
}
}
}
}
}
It avoids the additional indirection of a Loader.
You can try a concept of qml Loader that may match your requirements. Basically, you cannot define multiple delegates for a single view. So setting your Top Delegate as the loader and loading the items based on the type will help you with this case.
Positioning is also possible you can have x & y pos defined with your model data. So that you can align your view items accordingly. I have used Scrollview + Repeater in this case
Shared a minimal sample here. For sake of simplicity, I have kept my data with Qml ListModel. The same can be done with objects from the c++ model as well.
Here provided the source as a Qt solution project too.
// ShapeModel.qml //
import QtQuick 2.15
import QtQml.Models 2.15
ListModel {
ListElement {
type: "circle"
val: "100"
xpos: 10
ypos: 10
}
ListElement {
type: "rectangle"
val: "30"
xpos: 100
ypos: 100
}
ListElement {
type: "circle"
val: "150"
xpos: 300
ypos: 450
}
ListElement {
type: "rectangle"
val: "20"
xpos: 500
ypos: 200
}
ListElement {
type: "circle"
val: "25"
xpos: 650
ypos: 100
}
ListElement {
type: "rectangle"
val: "60"
xpos: 600
ypos: 200
}
}
// main.qml //
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 640
height: 480
visible: true
title: qsTr("Qt MVC")
Component {
id: componentCircleId
Rectangle {
border.color: "blue"
border.width: 1
height: value
width: value
radius: value/2
}
}
Component {
id: componentRectangleId
Rectangle {
border.color: "orange"
border.width: 1
height: value
width: value
}
}
Component {
id: componentLoaderId
Loader {
property real value: val
x: xpos
y: ypos
sourceComponent: type === "circle" ?
componentCircleId : componentRectangleId
}
}
ScrollView {
id: scrollviewId
anchors.fill: parent
Repeater {
anchors.fill: parent
model: ShapeModel{}
delegate: componentLoaderId
onItemAdded: {
// scroll area computation
// Better solutions may be available
if(item.x+item.width > scrollviewId.contentWidth)
scrollviewId.contentWidth = item.x+item.width
if(item.y+item.height > scrollviewId.contentHeight)
scrollviewId.contentHeight = item.y+item.height
}
}
}
}
I want to do something very similar to this image in Qt where I can click in any square and change the color of it.
It's pretty simple to do this with QML. Look at the code below:
import QtQuick 2.1
import QtQuick.Window 2.0
Window {
id: root
visible: true
width: 360
height: 500
Column{
Repeater{
model: getRowsNumber(root.height)
delegate: Row{
property int externalIdx: index
Repeater{
model: getColumnsNumber(root.width)
delegate: Rectangle{
property bool selected: false
property color originalColor: (index + externalIdx) % 2 == 0 ? "black" : "white"
width:20
height: 20
color: selected ? "red" : originalColor
border.width: 1
border.color: "black"
MouseArea{
anchors.fill: parent
onClicked: parent.selected = !parent.selected
}
}
}
}
}
}
function getColumnsNumber(width){
return width/20;
}
function getRowsNumber(height){
return height/20;
}
}
That's all you need to have a rectangular chess-like grid where each cell changes its color when it is clicked on. Of course, you will need to adapt it to your needs but that should be enough for you to start.
I am developing an application with Qt and qml and I am building it for OSX, iOS and Android. The application contains a StackView which has only three pages. In the second page I have a five MenuItems and five Dialogs.
While this very simple application has an excellent performance when runs on OSX and Android, it delays up to 3-4 seconds to show the second page on iOS.
I have already tried "Qml Profiler" and I have found out that the delay happens during the creation of the qml file that describes the second page.
I have also tried "Qt Quick Compiler" (Commercial Qt) with no significant difference.
main.qml
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
ApplicationWindow {
id: app
title: "MyApp"
width: 1024
height: 768
visible: true
StackView {
id: stackView
anchors.fill: parent
initialItem: firstPage
}
Component {
id:firstPage
FirstPage {
}
}
}
firstPage.qml
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
Item {
id:firstPage
signal collectionClicked(string name)
focus: true
objectName: "firstPage"
Rectangle {
anchors.fill: parent
color: "blue"
}
MouseArea {
anchors.fill: parent
onClicked: {
stackView.push(Qt.resolvedUrl("SecondPage.qml"))
}
}
}
SecondPage.qml
import QtQuick 2.3
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
Item {
id: itemsPage
signal itemClicked()
Rectangle {
id: myRectangle
anchors.fill: parent
color: "red"
Dialog {
id: dialog1
height: 200
width: 300
//onAccepted: ;
}
Dialog {
id: dialog2
height: 200
width: 300
//onAccepted: ;
}
Dialog {
id: dialog3
height: 200
width: 300
//onAccepted: ;
}
Dialog {
id: dialog4
height: 200
width: 300
//onAccepted: ;
}
Dialog {
id: dialog5
height: 200
width: 300
//onAccepted: ;
}
Menu {
id: myMenu
title: "Menu"
MenuItem {
text: "Test"
//onTriggered: ;
}
MenuItem {
text: "Test"
//onTriggered: ;
}
MenuItem {
text: "Test"
//onTriggered: ;
}
MenuItem {
text: "Test"
//onTriggered: ;
}
MenuItem {
text: "Test"
//onTriggered: ;
}
}
}
MouseArea {
anchors.fill: parent
onClicked: stackView.push(Qt.resolvedUrl("ThirdPage.qml"));
}
}
ThirdPage.qml
import QtQuick 2.0
Item {
Rectangle {
anchors.fill: parent
color: "green"
Text {
text: "third page"
}
}
}
I have tried for such a long to find a solution. And I think that it is impossible that there is nobody that has ever tried to make a simple qml application for iOS and faced what I have faced.
Thank you,
Michael
In traditional Qt (QWidget) I have one QMainWindow and some dynamically created QWidgets with the content and I change them that one was seen in main windows. What are the ways of achieving when I have couple qml files and I wants to be able to switch between them when eg clicking a button.
There are at least 3 options for solving this problem:
You can use the ready for this purpose component StackView. The point is that you will create 2 components at once and you'll be able to change them by clicking on the button.
Example:
import QtQuick 2.12
import QtQuick.Controls 2.5
ApplicationWindow {
id: window
visible: true
width: 640
height: 480
title: qsTr("Stack")
header: ToolBar {
contentHeight: toolButton.implicitHeight
ToolButton {
id: toolButton
text: stackView.depth > 1 ? "\u25C0" : "\u2630"
font.pixelSize: Qt.application.font.pixelSize * 1.6
onClicked: {
if (stackView.depth > 1) {
stackView.pop()
} else {
drawer.open()
}
}
}
Label {
text: stackView.currentItem.title
anchors.centerIn: parent
}
}
Drawer {
id: drawer
width: window.width * 0.66
height: window.height
Column {
anchors.fill: parent
ItemDelegate {
text: qsTr("Page 1")
width: parent.width
onClicked: {
stackView.push("Page1Form.qml")
drawer.close()
}
}
ItemDelegate {
text: qsTr("Page 2")
width: parent.width
onClicked: {
stackView.push("Page2Form.qml")
drawer.close()
}
}
}
}
StackView {
id: stackView
initialItem: "HomeForm.qml"
anchors.fill: parent
}
}
Use the Loader here you will dynamically load files during execution, the disadvantage of this method is that if you switch often, it will be time consuming.
Example:
import QtQuick 2.0
Item {
width: 200; height: 200
Loader { id: pageLoader }
MouseArea {
anchors.fill: parent
onClicked: pageLoader.source = "Page1.qml"
}
}
You can create a class in C++ that will be given to an already initialized QML object to an empty qml form. thus, mono place individual components into libraries and use them as plugins (use qqmlcomponent).
I would use a simple Loader, with a button that when is clicked, changes the source file of the Loader. Like so :
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
id: root
Rectangle {
id: page_main
color: "#202020"
anchors.fill : parent
Button {
id: button_page_1
width: 105
text: qsTr("Page 1")
anchors {
left: parent.left
top: parent.top
leftMargin: 6
topMargin: 0
}
checkable: true
onClicked: {
if (loader_main.source == "/page_1.qml") {
loader_main.source = ""
} else {
loader_main.source = "/page_1.qml"
button_page_2.checked = false
button_page_3.checked = false
}
}
}
Button {
id: button_page_2
width: 105
text: qsTr("Page 2")
anchors {
left: button_auto.right
top: parent.top
leftMargin: 6
topMargin: 0
}
checkable: true
onClicked: {
if (loader_main.source == "/page_2.qml") {
loader_main.source = ""
} else {
loader_main.source = "/page_2.qml"
button_page_1.checked = false
button_page_3.checked = false
}
}
}
Button {
id: button_page_3
width: 105
text: qsTr("Page 3")
anchors {
left: button_manual.right
top: parent.top
leftMargin: 6
topMargin: 0
}
checkable: true
onClicked: {
if (loader_main.source == "/page_page_3.qml") {
loader_main.source = ""
} else {
loader_main.source = "/page_page_3.qml"
button_page_1.checked = false
button_page_2.checked = false
}
}
}
}
Loader {
id: loader_main
y: 60
visible: true
anchors {
top: parent.top
bottom: parent.bottom
right: parent.right
left: parent.left
topMargin: 48
bottomMargin: 0
leftMargin: 0
rightMargin: 0
}
}
}