I am writing a QML application that loads and displays images, I have a central frame that show the image the user is currently looking at as well as a sidebar that lets the user select images. What I am having trouble with is getting the information about what the currently selected item is in the sidebar (ListView).
Additionally, I am having trouble accessing the sourceChanged signal in the delegate for the ListView and therefore can't update the images in the list without the user scrolling down and then back up to force them to reload. Is there any easy way to access these attributes even though they are nested within a ListView?
Here is the code for my ListView. The issue is that I want to be able to access the Image from outside of the ListView in order to send a sourceChanged signal but I'm not sure how you would access a specific item in the list.
//The list of frames that have been loaded
ListView {
id: frameList
anchors {top: parent.top; bottom: parent.bottom; left: frameViewer.right; leftMargin: 5; topMargin: 5}
spacing: 5
width:300
height: parent.height
Component {
id: frameDelegate
Rectangle {
id: wrapper
anchors {horizontalCenter: parent.horizontalCenter}
height: 300
width: 300
//If there is an image in that space and if it's selected, highlight it
color: frames[number] === undefined ? "white" : wrapper.ListView.view.currentIndex === index ? "yellow" : "white"
Image {
id: image
height: 280
width: 280
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
source: "image://images/" + frames[number]
}
MouseArea {
height: 280
width: 280
anchors.fill: image
hoverEnabled: true
onClicked: frames[index] === undefined ? console.log(""): wrapper.ListView.view.currentIndex = index
}
}
}
ListModel {
id: frameModel
ListElement {
number: 0
}
ListElement {
number: 1
}
ListElement {
number: 2
}
ListElement {
number: 3
}
ListElement {
number: 4
}
ListElement {
number: 5
}
}
model: frameModel
delegate: frameDelegate
focus: true
}
Ok so I fixed the issue, although I may not have done it in the best way possible.
My solution is to create a signal newFrame in the window which would be sent every time a new frame is loaded. Then I use a Connections in the Image to update the source, every time that signal is sent.
signal newFrame()
//Stores all the fileURLs
function loadImages()
{
for(var i = 0; i < fileDialog.fileUrls.length; i++)
{
var exists = false
for(var j = 0; j < frames.length; j++)
{
if(frames[j] === fileDialog.fileUrls[i])
{
exists = true
}
}
if(exists == false)
{
if(frames == [])
{
frames = fileDialog.fileUrls[i]
}
else
{
frames.push(fileDialog.fileUrls[i])
}
frame.sourceChanged(frame.source)
newFrame()
}
}
}
Component
{
id: frameDelegate
Rectangle
{
id: wrapper
anchors {horizontalCenter: parent.horizontalCenter}
height: 300
width: 300
//If there is an image in that space and if it's selected, highlight it
color: frames[number] === undefined ? "white" : wrapper.ListView.view.currentIndex === index ? "yellow" : "white"
Image
{
id: image
height: 280
width: 280
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
source: "image://images/" + frames[number]
Connections {
target: window
onNewFrame: {
image.source = "image://images/" + frames[number]
}
}
}
MouseArea
{
height: 280
width: 280
anchors.fill: image
hoverEnabled: true
onClicked: {frames[index] === undefined ? console.log(""): wrapper.ListView.view.currentIndex = index; frame.sourceChanged(frame.source)}
}
}
}
Related
I have implemented the following section
{
id: idLeftArrow
.
.
.
.
}
Row
{
id: idIpEditModeItem
anchors.left: idLeftArrow.right
visible: true
Repeater
{
id: idIpHighlightRepeater
model: 12
Text
{
id: idDigits
text: "0"
font.pointSize: 10
color: "yellow"
}
}
}
Image
{
id: idIpHiglight_Image
width: editModeIPWidth
height: editModeIPHeight
x: idIpHighlightRepeater.itemAt(ipCurrSelectedDigitIndex).x
y: idIpHighlightRepeater.itemAt(ipCurrSelectedDigitIndex).y
visible: false
source: "focus.png"
}
Here I am getting output like this
But I want output like this(there will be a gap between each character)
Also I have a idIpHiglight_Image which is using to highlight each digit. On launch I need output like this
But in my case the highlight is not getting set to the proper location. I am getting output something like this
Could anyone please help me to set the output exactly like this:
Also, on each left and right key press, I need to move the cursor properly to next/previous digit.
I wrote code like
onIpCurrSelectedDigitIndexChanged:
{
if( idIpHighlightRepeater.count == ipCurrSelectedDigitIndex)
{
ipCurrSelectedDigitIndex = 0
}
else if( 0 > ipCurrSelectedDigitIndex)
{
ipCurrSelectedDigitIndex = idIpHighlightRepeater.count - 1
}
}
After executing the code, I am getting error like
[W] (qrc:/common/qml/controls/CustomItem.qml:120) qrc:/common/qml/controls/EditListItem.qml:120: TypeError: Type error
[W] (qrc:/common/qml/controls/CustomItem.qml:119) qrc:/common/qml/controls/EditListItem.qml:119: TypeError: Type error
This the lines were i am getting the above error
I would do 2 different Components for the number and for the delimeter, something like this:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
id: main
visible: true
width: 600
height: 400
Component {
id: number
Text
{
text: "0"
font.pointSize: 16
color: "yellow"
padding: 5
Rectangle {
anchors.fill: parent
color: "transparent"
border { width: 3; color: "orange" }
visible: itemIndex == itemSelected
}
}
}
Component {
id: delimeter
Text
{
text: "."
font.pointSize: 16
color: "yellow"
}
}
Rectangle
{
id: rect
property int selected: -1;
color: "black"
anchors.centerIn: parent
width: layout.width
height: layout.height
Row {
id: layout
Repeater
{
id: repeater
model: 15
delegate: Loader {
id: loader
property int itemSelected: rect.selected;
property int itemIndex: index;
sourceComponent: ((index + 1) % 4 === 0) ? delimeter : number
}
}
}
}
Timer {
interval: 1000
repeat: true
running: true
onTriggered: {
if(rect.selected >= 15)
rect.selected = 0;
else
rect.selected ++;
}
}
}
the result:
My main.qml:
Window
{
visible: true
width: 640
height: 480
color: "grey"
GridLayout
{
anchors.fill: parent
columns : 2
rows : 2
Repeater
{
id: rectRepeater
model: 3
TextField
{
text: "hi"
}
}
}
Rectangle
{
id: r1
width: 100
height: 100
x: 200
y: 200
border.color: "red"
Text
{
id: t1
}
}
Component.onCompleted:
{
t1.text= rectRepeater.itemAt(0).text
}
}
The Text in the rectangle r1 displays the text at the start, but if I enter new text to the TextField, the rectangle will not be updated. How can I solve this?
A more elegant and maintainable solution is to implement a model that reflects the changes, and then make a binding of the first element with the text that shows Text:
Window{
visible: true
width: 640
height: 480
color: "grey"
ListModel{
id: mymodel
}
Component.onCompleted: {
for(var i=0; i<3; i++){
mymodel.append({"text" : "hi"})
}
}
GridLayout{
anchors.fill: parent
columns : 2
rows : 2
Repeater{
model: mymodel
TextField{
id: tf
onTextChanged: model.text = tf.text
Component.onCompleted: tf.text= model.text
}
}
}
Rectangle{
id: r1
width: 100
height: 100
x: 200
y: 200
border.color: "red"
Text {
id: t1
text: mymodel.count > 1 ? mymodel.get(0).text : ""
}
}
}
What you want, is to create a binding between the two.
Component.onCompleted:
{
t1.text = Qt.binding(function() { return rectRepeater.itemAt(0).text })
}
That being said, we would need to know exactly what you are trying to do, because creating bindings manually is an anti-pattern when not required. It is much better to bind directly, or to use signals.
Do you need the first elements, and the repeater, or is this just an test for you? What is your UI and what are you trying to achieve? This is some context worth giving for a proper answer.
One possible simpler solution
Repeater
{
id: rectRepeater
model: 3
TextField
{
text: "hi"
// See also `onEditingFinished` and `onValidated`
onTextChanged: {
if (index == 0)
t1.text = text
}
}
}
For more details about the property thing, look at my answers from your other question: Qml Repeater with ids
I have a Flow layout where I add items dynamically on user actions. In the same way I remove these items on user actions. The Flow QML component seems to work as expected until an item is removed. The item itself is removed, but the space it occupied is just blank. My intuition tells me the graphical item itself got deleted, but the view is not updating when items are removed.
Is the dynamical deletion of child items outside the scope of the Flow Component? Is there any other layout that behaves equally? GridLayout seems to be the closest, but it does not automatically wrap child items when the layout is resized.
Is there any non-hack way to enable Flow to rearrange when child item is disabled? If not, and if GridLayout is my best shot, how to make it wrap its child items like Flow does?
The code below demonstrates what I want to achieve:
Item {
id: root
Flow {
id: layout
anchors.fill: parent
Loader { id: loader }
}
MouseArea {
anchors.top: parent.top
height: parent.height / 2
width: parent.width
onClicked: loader.source = "SomeQmlComponent.qml"
}
MouseArea {
anchors.bottom: parent.bottom
height: parent.height / 2
width: parent.width
onClicked: loader.source = ""
}
}
Don't use Loader inside Flow. In your case items are parented to Loader and not to Flow so you lose all advantage of that. In normal way items are added and removed w/o problem:
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
width: 600
height: 600
visible: true
Component {
id: element
Rectangle {
width: Math.round(Math.random() * 100) + 50
height: Math.round(Math.random() * 100) + 50
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1)
}
}
Flow {
id: flow
spacing: 2
anchors.fill: parent
add: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutBack }
}
move: add
}
Timer {
id: timer
property bool is_add: true
interval: 300
repeat: true
running: true
onTriggered: {
if(timer.is_add) {
element.createObject(flow);
if(flow.children.length > 20) {
timer.is_add = false;
}
} else {
var item = flow.children[0];
item.destroy();
if(flow.children.length <= 1) {
timer.is_add = true;
}
}
}
}
}
#folibis - thanks for the answer, it helped me with a problem I was trying to solve which was to dynamically add elements and have them resize to the screen. I took your example and made the rectangles fill the width and the height is filled by the rectangles with their height being even. So the shrink/expand with the number of rectangles. I reduced it to 4 rectangles for simplicity and made it remove a rectangle at random.
Component {
id: element
Rectangle {
width: flow.width
height: flow.height/flow.children.length
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1)
}
}
Flow {
id: flow
spacing: 2
anchors.fill: parent
add: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutBack }
}
move: add
}
Timer {
id: timer
property bool is_add: true
interval: 1000
repeat: true
running: true
onTriggered: {
if(timer.is_add) {
element.createObject(flow);
if(flow.children.length > 3) {
timer.is_add = false;
}
} else {
var i = Math.floor(Math.random() * Math.floor(flow.children.length ));
console.log(i)
var item = flow.children[i];
item.destroy();
if(flow.children.length <= 1) {
timer.is_add = true;
}
}
}
}
When I try to change a property value of an item contained into a ListModel the following code has no effect:
Main.qml
import QtQuick 2.0
Item {
anchors.fill: parent
ListModel { id: modelCrayon }
Component.onCompleted: {
for (var i = 0; i < 10; i++)
modelCrayon.append( { _tag: i, _source: "resources/crayon-green.png", _selected: false } )
}
Column {
x: -170
spacing: 0
Repeater {
model: modelCrayon
delegate: Crayon {
tag: _tag
source: _source
selected: _selected
onCrayonSelected: {
for (var i = 0; i < modelCrayon.count; i++) {
if (i == tag) continue;
modelCrayon.setProperty(i, "_selected", false);
}
}
}
}
}
}
Crayon.qml
import QtQuick 2.0
Image {
property bool selected
property int tag
signal crayonSelected()
id: crayon
smooth: true
fillMode: Image.PreserveAspectFit
onSelectedChanged: console.debug(tag, selected)
MouseArea {
anchors.fill: parent
onClicked: {
selected = !selected
if (selected) crayonSelected()
}
}
states: State {
name: "selected"; when: selected == true
PropertyChanges { target: crayon; x: 30 }
}
transitions: Transition {
from: ""; to: "selected"
PropertyAnimation { property: "x"; duration: 500; easing.type: Easing.InOutQuad }
}
}
Nothing is shown on console, so the "selected" var is never changed.
I'm sure there's something obvious I'm missing.
By the way, is there a smarter way to use a ListModel as a OptionBox? I mean I want only ONE item at time must have the selected property == true. Or, in other words, keep tracks of the selected index.
This is a working code to achieve what I asked. But it doens't answer why the property was not set.
ListView {
id: list
anchors.verticalCenter: parent.verticalCenter
height: parent.height
x: -150
spacing: 0
orientation: ListView.Vertical
focus: true
model: modelCrayon
delegate: Crayon {
id: delegate
source: _source
selected: ListView.isCurrentItem
MouseArea {
anchors.fill: parent
onClicked: list.currentIndex = index
}
}
}
I have tested your sample code (the Column version), and it works well with Qt 5.4 / Windows 7 64bit.
What is your running environment?
I am developing a QML application where I have three main views. My code looks like below.
SplitView{
ListView{
id: firstView
}
ListView{
id: secondView
}
WebView{
id: thirdView
}
}
Now this is working fine, but I would like to do this: when my main window is resized below a certain width (500) I would like to show only one view, so that clicking on the delegate will show the next view (with the possibility of going back to the previous view). So for example clicking on the first view will show the second view and clicking on the second view will show the third view. The approach I want is very similar to the Mail application in Windows 10.
Does anyone know how can this be achieved in QML?
OK, I've cooked up a crude example, I didn't modularize it deliberately, so you can see how it works in a single source. Also, I don't know how the windows 10 mail application does it, since I don't have it, but it still close enough to your description.
You begin with 3 list views in a row, which are sized to fill the entire UI, but if you reduce the UI size to the minimum of 500, the views will increase in size to fill almost the entire ui, and when you click on a view item, it will move you to the next view, and if you click the showing previous view, you will be returned back to it.
ApplicationWindow {
title: qsTr("Hello World")
width: 800
height: 300
minimumWidth: 500
visible: true
Item {
id: adapt
width: parent.width
height: parent.height
property int sizeUnit: width > 500 ? width / 5 : 400
property bool isPaged: width == 500 ? true : false
onIsPagedChanged: { if (!isPaged) { x = 0; page = 0; } }
property int page: 0
Behavior on x { NumberAnimation { duration: 250; easing.type: Easing.OutBack } }
ListModel {
id: mod
ListElement { name: "one" }
ListElement { name: "two" }
ListElement { name: "three" }
ListElement { name: "four" }
ListElement { name: "five" }
}
ListView {
id: v1
width: adapt.sizeUnit
height: parent.height
model: mod
delegate: Rectangle {
height: 70
width: v1.width
color: "red"
border.color: "black"
Text { anchors.centerIn: parent; text: name }
MouseArea {
anchors.fill: parent
onClicked: {
if (adapt.isPaged) {
if (adapt.page == 0) {
adapt.x = -(v2.x - 100)
adapt.page = 1
} else {
adapt.x = 0
adapt.page = 0
}
}
}
}
}
}
ListView {
id: v2
width: adapt.sizeUnit
height: parent.height
x: adapt.sizeUnit + 10
model: mod
delegate: Rectangle {
height: 70
width: v2.width
color: "cyan"
border.color: "black"
Text { anchors.centerIn: parent; text: name }
MouseArea {
anchors.fill: parent
onClicked: {
if (adapt.isPaged) {
if (adapt.page == 1) {
adapt.x = -(v3.x - 100)
adapt.page = 2
} else {
adapt.x = -(v2.x - 100)
adapt.page = 1
}
}
}
}
}
}
ListView {
id: v3
width: adapt.isPaged ? adapt.sizeUnit : 3 * adapt.sizeUnit - 20
height: parent.height
x: v2.x + v2.width + 10
model: mod
delegate: Rectangle {
height: 70
width: v3.width
color: "yellow"
border.color: "black"
Text { anchors.centerIn: parent; text: name }
}
}
}
}
This should be enough to get you going. Obviously, for production you can go for more elegant layouting and nagivation, such as use a function for the actual "page sliding" and anchors, the above is just for the purpose of example.
Here is my idea of how to solve the problem.
// When width of MainWindow is smaller than 500 stop using SplitView
property bool useSplitView: (width < 500 ? false : true)
onUseSplitViewChanged: {
if (useSplitView) {
firstView.anchors.fill = undefined
secondView.anchors.fill = undefined
thirdView.anchors.fill = undefined
firstView.parent = splitView // splitView is id of SplitView
secondView.parent = splitView
thirdView.parent = splitView
}
else {
firstView.parent = mainWindow.contentItem // mainWindow is id of MainWindow
secondView.parent = mainWindow.contentItem
thirdView.parent = mainWindow.contentItem
secondView.visible = false
thirdView.visible = false
firstView.anchors.fill = firstView.parent
secondView.anchors.fill = secondView.parent
thirdView.anchors.fill = thirdView.parent
}
}
When useSplitView flag changes you need to enable some kind of touch area on views and switch between them on click.