I am trying to build an interactive map application which allow me to add and modify Map Items. I am able to add new map item but I still have problem to modify the model. On the code below, if I have more than one item, the remove function always delete the first item crated not the current item selected. I want to modify the model not only the view of the model, but how can I get the currentIndex of the model ?
ListModel {
id: mapModel
}
Map {
id: map
//...
MapItemView {
model: mapModel
delegate: MapCircle {
radius: 80000
color: 'blue'
center {
latitude: lat
longitude: longi
}
MouseArea {
onClicked: {
mapModel.remove(model.index)
}
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
var coord = map.toCoordinate(Qt.point(mouse.x,mouse.y))
mapModel.append({lat : coord.latitude, longi: coord.longitude});
}
}
}
Found the answer myself. Just use mapModel.remove(index) instead of mapModel.remove(model.index)
Related
Suppose I had the Component
Component {
id: myComp1
Item {
id: item
ListView {
id: listView
model : ListModel { id: listModel }
delegate : RowLayout { /* display model data*/ }
Component.onCompleted {
// get data from server ...
model.append(dataFromServer)
}
}
}
}
Then I have a second Component, which is another page in the stack, and I want to use this component to update mycomp1, i.e:
Component {
id: myComp2
Button {
onClicked: {
myComp1.item.listView.listModel.append(someNewData) // want to be able to do this
}
}
}
And these components are tied together in a StackView
Now, this doesnt seem to work since myComp2 cant seem to access the necessary scope to update the model of myComp1. Is there any way around this?
Thanks for the help.
The problem is that a Component is like a type declaration. It does not define an instance of an object, so you cannot access its members.
You could pull the ListModel outside of that Component so that both Components can access it.
ListModel {
id: listModel
}
Component {
id: comp1
ListView { model: listModel }
}
Component {
id: comp2
Button {
onClicked: { listModel.append(someNewData) }
}
}
I wanted to implement a function that users can drag the marker (which is defined as MapQuickItem) on the map and automatically change its path (which is defined as MapPolyline). Currently I can only drag the marker but don't know how to change its path.
If I want to define a DropArea under the Map and call the MapPolyline.removeCoordinate() function to change the path, how to visit the index in the delegate? And I'm not sure if this idea will work.
Here is the code:
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
zoomLevel: 14
activeMapType: supportedMapTypes[7]
/* maker */
MouseArea {
anchors.fill: parent
onClicked: {
var crd = map.toCoordinate(Qt.point(mouseX, mouseY))
console.log(crd)
markerModel.append({ "latitude": crd.latitude, "longitude": crd.longitude})
line.addCoordinate(crd)
}
}
MapItemGroup {
MapPolyline {
id: line
line.width: 3
line.color: "#515151"
}
MapItemView {
add: Transition {}
remove: Transition {}
model: ListModel {
id: markerModel
}
delegate:
MapQuickItem {
id: marker
coordinate: QtPositioning.coordinate(latitude, longitude)
anchorPoint: Qt.point(markerImage.width * 0.5, markerImage.height * 0.5)
sourceItem: Image {
id: markerImage
z: 5
width: 30
height: 30
source: index <= 0 ? "Images/starting point.svg" : "Images/point black.svg"
MouseArea {
anchors.fill: parent
onClicked: {
line.removeCoordinate(index);
markerModel.remove(index);
}
drag.target: marker
}
}
}
/* maker */
}
}
}
I have tried several methods to solve the problem.
This method is not feasible cos the event (drag.onActiveChanged) is triggered only at the moment that the drag event happens.
drag.onActiveChanged: {
if(mouseArea.drag.active) {
line.replaceCoordinate(index, marker.coordinate);
}
}
I tried to define a DropArea under the Map and called the drag.ondragStarted() function to trigger the event, but I didn't figure out how to visit the index in the delegate, then I gave up.
It worked! When I dragged the marker on the map, the path automatically changed! The event (onPositionChanged) is triggered everytime the coordinate of marker changes.
onPositionChanged: {
line.replaceCoordinate(index, marker.coordinate);
}
Thank myself :-)
Inside the delegate, I bind Image's source property to ListView's currentIndex which determines which image to load. This works great:
ListView {
id: answerListView
model: 5
currentIndex: -1
delegate: answerDelegate
}
Component {
id: answerDelegate
Item {
width: 100
height: 100
Image {
source: answerListView.currentIndex === index
? "selected.png" : "not_selected.png"
}
MouseArea {
anchors.fill: parent
onClicked: {
answerListView.currentIndex = index
}
}
Component.onCompleted: {
answerListView.currentIndex = 1; // doesn't work!!
}
}
}
Since currentIndex: -1, it will always show not_selected.png. To show selected.png, I change currentIndex in Component.onLoaded inside delegate.
I was expecting image to load selected.png since currentIndex was updated.
What is the correct way and what am I misunderstanding here?
Ok, new guess:
You want to have the posibility to select multiple Items. As currentIndex only stores one value, which is the value you assigned it last, you can use it to mark only one Item.
Therefore you need to find another way to store your selection. You might for example have a property in the delegate: property bool selected: false which you set to true upon selection.
The problem with this solution is, that it only works if all Items are instantiated at all times. As soon as one Item will be destroyed again, the information will be lost, and uppon the next creation, the selection/unselection is undone.
The better way would be to introduce a role in your model, that stores the selection outside of the non-persistant delegates for you:
ListView {
id: answerListView
model: lm
delegate: answerDelegate
width: 100
height: 220
}
ListModel {
id: lm
ListElement { selected: false }
ListElement { selected: false }
ListElement { selected: false }
ListElement { selected: false }
ListElement { selected: false }
}
Component {
id: answerDelegate
Item {
width: 100
height: 100
Image {
anchors.fill: parent
source: model.selected ? "selected.png" : "notselected.png"
}
Text {
text: (model.selected ? 'selected ' : 'notselected ')
}
Component.onCompleted: {
model.selected = true // doesn't work!!
}
MouseArea {
anchors.fill: parent
onClicked: {
model.selected = !model.selected
}
}
}
}
Another option would probably be a ItemSelectionModel, but I don't know atm, how it works.
Otherwise your example works as expected:
The Item with index 1 is shown, and displays the Image selected.png. All other Items are not shown (for the ListView is to small) but if the would be shown, they would show notselected.png for the answerListView.currentIndex is not equal to their index.
Is it possible to create QML Items out of a DelegateModel?
Here is a example DelegateModel:
DelegateModel
{
id: delegateModel
model: ListModel
{
ListElement { name: "#FAFAFA"; test: "object1" }
ListElement { name: "#000000"; test: "object2" }
}
delegate: Rectangle
{
objectName: test
width: 50
height: 50
color: name
}
Component.onCompleted:
{
Utils.var_dump(items,3)
items.create(0)
Utils.var_dump(items.get(0),3)
}
}
The Result should look like this:
Rectangle
{
objectName: "object1"
width: 50
height: 50
color: "#FAFAFA"
}
Rectangle
{
objectName: "object2"
width: 50
height: 50
color: "#000000"
}
For every ListElement there is a created delegate with the inserted ListElement data.
You can do that with anything that is usable to instantiate a Model (a View)
For example you could use it as a model for a ListView, a GridView or a Repeater. As the model provides the delegate on its own, you do not need to specify any delegate in the View, that instantiates it.
Column {
Repeater {
model: delegateModel
// delegate: ... <--- Nothing here! Uses the delegate from the Model.
}
}
If you use the create(index)-Method, the delegate will be created, but has no parent, so it is not displayed. So you need to set the parent, to have it shown:
Button {
onClicked: {
for (var a = 0; a < dm.items.count; a++) {
var o = dm.items.create(a)
o.parent = r
}
}
}
You need to be aware, that the DelegateModel (without Package and Parts) can't be used in multiple views, as each entry/delegate can be instantiated only once at the same time. If you want to have that,
consider using a QSortFilterProxyModel to filter the stuff, and use as much Views that provide their own delegates, as you want.
I have QtQuick 1.0
I use the following code:
Rectangle {
Component {
id: appDelegate
MouseArea{
id:myMouseArea
hoverEnabled: true
onClicked:{
onClicked: load.source = page;
}
}
Loader {
id: load
}
}
GridView {
id: view
// I am unable to access myMouseArea here.
highlight: myMouseArea.containsMouse ? appHighlight : !appHighlight
delegate: appDelegate
}
}
It gives me the following error:
ReferenceError: Can't find variable: myMouseArea
/usr/lib/i386-linux-gnu/qt4/bin/qmlviewer exited with code 0
I don't know if the details I provided are sufficient, please let me know if theres anything else I am missing.
I am using this code as an example:
http://docs.knobbits.org/qt4/declarative-modelviews-gridview-qml-gridview-example-gridview-example-qml.html
You cannot access myMouseArea because it's created inside delegate context. You cannot access delegate other then currentItem. But you can freely access view inside the context of delegate, to set currentIndex to attached property index.
This is a corrected code:
Rectangle {
width: 360
height: 360
Component { // It must be a component, if we want use it as delegate
id: appDelegate
// its not possible to have more than one element inside component
Rectangle
{
// need to set size of item, anchors wont work here
// could use view.cellWidth and view.cellHeight to keep it DRY
width: 96
height: 66
color: "green" // color only to see the place of MouseArea
MouseArea {
id:myMouseArea
anchors.fill: parent // this setup the size to whole rectangle
// it this item have the size 0,0 it will simple do not work
hoverEnabled: true
onEntered: {
// we know the mouse is inside this region
// setting this value will show the highlight rectangle
view.currentIndex = index;
}
onClicked:{
onClicked: load.source = page;
}
}
Loader {
// this is not needed but it's wise to not keep zero size
anchors.fill: parent
id: load
}
}
}
GridView {
id: view
// the size of GridView must be set,
// as otherwise no delegate will not show
anchors.fill: parent
anchors.margins: 5
cellWidth: 100
cellHeight: 70
// Rectangle will act as a border.
// Size and position is set by GridView
// to the size and position of currentItem.
// This is no a item, this makes a Component
// as highlight property needs one.
// You can create a Component like appDelegate.
highlight : Rectangle {
border.width: 2
border.color: "blue"
}
// some ListModel to setup the page variable inside delegate context
model: ListModel {
ListElement { page: "test1.qml"; }
ListElement { page: "test2.qml"; }
ListElement { page: "test3.qml"; }
}
delegate: appDelegate
}
}