Qt Quick Controls 2 on-screen Number pad on spinbox - qt

In a touchpanel application, is it possible to edit the integer value of a qml SpinBox, from QtQuickControls 2.0, in such a way that a numeric keypad appears on-screen and one can enter the exact value?
Or do you have any idea on how to embed this predefined number pad in a customized spinbox, that should pop-up when the user taps on the integer number?

The numpad can be set to be invisible and put on top of everything, then you can have a function to enable its visibility and set it's target. When you are done with typing the number, you set the target value to the numpad value, and hide it again. This way you can target different spinboxes.
As of the actual method to request the keypad, you can put a MouseArea to fill the spinbox on top of it. Or you can make the mouse area narrower, so that the plus/minus buttons of the spinbox are still clickable.
Also keep in mind that numpad you have linked is not actually functional.

Thanks for your suggestion. I came up with a first quick solution based on your idea and the number pad from the example section. I post it here, just in case it helps someone else as starting point. I am a QML beginner, so happy to get any improvements or corrections.
See attached screenshot of the numeric virtual touchpad appearing when clicking on the spinbox number
SpinBox {
id: boxCommPos
x: 50
y: 50
z: 0
width: 200
height: 50
to: 360
editable: false
contentItem: TextInput {
z: 1
text: parent.textFromValue(parent.value, parent.locale)
font: parent.font
//color: "#21be2b"
//selectionColor: "#21be2b"
//selectedTextColor: "#ffffff"
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
readOnly: !parent.editable
validator: parent.validator
inputMethodHints: Qt.ImhFormattedNumbersOnly
}
Label {
id: commUnits
x: 125
y: 0
z: 3
text: qsTr("º")
font.pointSize: 16
anchors.right: parent.right
anchors.rightMargin: 45
}
MouseArea {
id: padCommPos
x: 40
y: 0
z: 2
width: parent.width-x-x
height: 50
onClicked: touchpad.visible=true
}
}
NumberTouchpad {
id: touchpad
x: 470
y: -20
z: 1
scale: 0.90
visible: false
Rectangle {
id: backrect
x: -parent.x/parent.scale
z: -1
width: parent.parent.width/parent.scale
height: parent.parent.height/parent.scale
color: "#eceeea"
opacity: 0.5
MouseArea {
anchors.fill: parent
}
}
}
The file NumberPad.qml
import QtQuick 2.0
Grid {
columns: 3
columnSpacing: 32
rowSpacing: 16
signal buttonPressed
Button { text: "7" }
Button { text: "8" }
Button { text: "9" }
Button { text: "4" }
Button { text: "5" }
Button { text: "6" }
Button { text: "1" }
Button { text: "2" }
Button { text: "3" }
Button { text: "0" }
Button { text: "."; dimmable: true }
//Button { text: " " }
Button { text: "±"; color: "#6da43d"; operator: true; dimmable: true }
//Button { text: "−"; color: "#6da43d"; operator: true; dimmable: true }
//Button { text: "+"; color: "#6da43d"; operator: true; dimmable: true }
//Button { text: "√"; color: "#6da43d"; operator: true; dimmable: true }
//Button { text: "÷"; color: "#6da43d"; operator: true; dimmable: true }
//Button { text: "×"; color: "#6da43d"; operator: true; dimmable: true }
Button { text: "C"; color: "#6da43d"; operator: true }
Button { text: "✔"; color: "#6da43d"; operator: true; dimmable: true }
Button { text: "X"; color: "#6da43d"; operator: true }
}
Display.qml
// Copyright (C) 2015 The Qt Company Ltd.
import QtQuick 2.0
import QtQuick.Window 2.0
Item {
id: display
property real fontSize: Math.floor(Screen.pixelDensity * 10.0)
property string fontColor: "#000000"
property bool enteringDigits: false
property int maxDigits: (width / fontSize) + 1
property string displayedOperand
property string errorString: qsTr("ERROR")
property bool isError: displayedOperand === errorString
function displayOperator(operator)
{
listView.model.append({ "operator": operator, "operand": "" })
enteringDigits = true
listView.positionViewAtEnd()
//console.log("display",operator);
}
function newLine(operator, operand)
{
displayedOperand = displayNumber(operand)
listView.model.append({ "operator": operator, "operand": displayedOperand })
enteringDigits = false
listView.positionViewAtEnd()
//console.log("newLine",operator);
}
function appendDigit(digit)
{
if (!enteringDigits)
listView.model.append({ "operator": "", "operand": "" })
var i = listView.model.count - 1;
listView.model.get(i).operand = listView.model.get(i).operand + digit;
enteringDigits = true
listView.positionViewAtEnd()
//console.log("num is ", digit);
}
function setDigit(digit)
{
var i = listView.model.count - 1;
listView.model.get(i).operand = digit;
listView.positionViewAtEnd()
//console.log("setDigit",digit);
}
function clear()
{
displayedOperand = ""
if (enteringDigits) {
var i = listView.model.count - 1
if (i >= 0)
listView.model.remove(i)
enteringDigits = false
}
//console.log("clearing");
}
// Returns a string representation of a number that fits in
// display.maxDigits characters, trying to keep as much precision
// as possible. If the number cannot be displayed, returns an
// error string.
function displayNumber(num) {
if (typeof(num) != "number")
return errorString;
var intNum = parseInt(num);
var intLen = intNum.toString().length;
// Do not count the minus sign as a digit
var maxLen = num < 0 ? maxDigits + 1 : maxDigits;
if (num.toString().length <= maxLen) {
if (isFinite(num))
return num.toString();
return errorString;
}
// Integer part of the number is too long - try
// an exponential notation
if (intNum == num || intLen > maxLen - 3) {
var expVal = num.toExponential(maxDigits - 6).toString();
if (expVal.length <= maxLen)
return expVal;
}
// Try a float presentation with fixed number of digits
var floatStr = parseFloat(num).toFixed(maxDigits - intLen - 1).toString();
if (floatStr.length <= maxLen)
return floatStr;
return errorString;
}
Item {
id: theItem
width: parent.width
height: parent.height
Rectangle {
id: rect
x: 0
color: "#eceeea"
height: parent.height
width: display.width
}
/*Image {
anchors.right: rect.left
source: "images/paper-edge-left.png"
height: parent.height
fillMode: Image.TileVertically
}
Image {
anchors.left: rect.right
source: "images/paper-edge-right.png"
height: parent.height
fillMode: Image.TileVertically
}
Image {
id: grip
source: "images/paper-grip.png"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
}*/
ListView {
id: listView
width: display.width
height: display.height
delegate: Item {
height: display.fontSize * 1.1
width: parent.width
Text {
id: operator
font.pixelSize: display.fontSize
color: "#6da43d"
text: model.operator
}
Text {
id: operand
y:5
font.pixelSize: display.fontSize
color: display.fontColor
anchors.right: parent.right
anchors.rightMargin: 22
text: model.operand
}
}
model: ListModel { }
}
}
}
calculator.qs
// Copyright (C) 2015 The Qt Company Ltd.
var curVal = 0
var memory = 0
var lastOp = ""
var previousOperator = ""
var digits = ""
function disabled(op) {
if (op == "✔")
display.fontColor="#000000"
if (op=="X")
return false
else if (op == "✔" && (digits.toString().search(/\./) != -1 || digits.toString().search(/-/)!= -1 || parseInt(digits)>359)) {
display.fontColor="#ff0000"
return true
}
else if (digits == "" && !((op >= "0" && op <= "9") || op == "."))
return true
else if (op == '=' && previousOperator.length != 1)
return true
else if (op == "." && digits.toString().search(/\./) != -1) {
return true
} else if (op == "√" && digits.toString().search(/-/) != -1) {
return true
} else {
return false
}
}
function digitPressed(op)
{
if (disabled(op))
return
if (digits.toString().length >= display.maxDigits)
return
if (lastOp.toString().length == 1 && ((lastOp >= "0" && lastOp <= "9") || lastOp == ".") ) {
digits = digits + op.toString()
display.appendDigit(op.toString())
} else {
digits = op
display.appendDigit(op.toString())
}
lastOp = op
}
function operatorPressed(op)
{
if (disabled(op))
return
lastOp = op
if (op == "±") {
digits = Number(digits.valueOf() * -1)
display.setDigit(display.displayNumber(digits))
return
}
if (previousOperator == "+") {
digits = Number(digits.valueOf()) + Number(curVal.valueOf())
} else if (previousOperator == "−") {
digits = Number(curVal.valueOf()) - Number(digits.valueOf())
} else if (previousOperator == "×") {
digits = Number(curVal) * Number(digits.valueOf())
} else if (previousOperator == "÷") {
digits = Number(curVal) / Number(digits.valueOf())
}
if (op == "+" || op == "−" || op == "×" || op == "÷") {
previousOperator = op
curVal = digits.valueOf()
digits = ""
display.displayOperator(previousOperator)
return
}
if (op == "=") {
display.newLine("=", digits.valueOf())
}
curVal = 0
previousOperator = ""
if (op == "1/x") {
digits = (1 / digits.valueOf()).toString()
} else if (op == "x^2") {
digits = (digits.valueOf() * digits.valueOf()).toString()
} else if (op == "Abs") {
digits = (Math.abs(digits.valueOf())).toString()
} else if (op == "Int") {
digits = (Math.floor(digits.valueOf())).toString()
} else if (op == "√") {
digits = Number(Math.sqrt(digits.valueOf()))
display.newLine("√", digits.valueOf())
} else if (op == "mc") {
memory = 0;
} else if (op == "m+") {
memory += digits.valueOf()
} else if (op == "mr") {
digits = memory.toString()
} else if (op == "m-") {
memory = digits.valueOf()
} else if (op == "backspace") {
digits = digits.toString().slice(0, -1)
display.clear()
display.appendDigit(digits)
} else if (op == "✔") {
window.visible=false
boxCommPos.value=parseInt(digits)
display.clear()
curVal = 0
memory = 0
lastOp = ""
digits = ""
} else if (op == "X") {
window.visible=false
display.clear()
curVal = 0
memory = 0
lastOp = ""
digits = ""
}
// Reset the state on 'C' operator or after
// an error occurred
if (op == "C" || display.isError) {
display.clear()
curVal = 0
memory = 0
lastOp = ""
digits = ""
}
}
Button.qml
// Copyright (C) 2015 The Qt Company Ltd.
import QtQuick 2.0
Item {
id: button
property alias text: textItem.text
property color color: "#eceeea"
property bool operator: false
property bool dimmable: false
property bool dimmed: false
width: 30
height: 50
Text {
id: textItem
font.pixelSize: 48
wrapMode: Text.WordWrap
lineHeight: 0.75
color: (dimmable && dimmed) ? Qt.darker(button.color) : button.color
Behavior on color { ColorAnimation { duration: 120; easing.type: Easing.OutElastic} }
states: [
State {
name: "pressed"
when: mouse.pressed && !dimmed
PropertyChanges {
target: textItem
color: Qt.lighter(button.color)
}
}
]
}
MouseArea {
id: mouse
anchors.fill: parent
anchors.margins: -5
onClicked: {
if (operator)
window.operatorPressed(parent.text)
else
window.digitPressed(parent.text)
}
}
function updateDimmed() {
dimmed = window.isButtonDisabled(button.text)
}
Component.onCompleted: {
numPad.buttonPressed.connect(updateDimmed)
updateDimmed()
}
}

Related

Binding loop detected for property "text"

My implementation is to keep track of whether I have placed the dot already •. I am trying to acheive the following: A • D • G • J • M • P • S • V • Z. The property binding for check, somehow it doesn't want to go back to 0. I also tried using a local var, but it will always be 0 or 1. How can I resolve this? I get the following error: Binding loop detected for property "text"
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
id: root
visible: true
anchors.fill: parent
property string letters: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
property int check: 0
readonly property real dx: 20
function midpoint(idx) {
return 20 + idx * root.dx;
}
function newLetters(index) {
if(root.letters[index] === "A"){
root.check = 0
return true
}
if(root.letters[index] === "D"){
root.check = 0
return true
}
if(root.letters[index] === "G"){
root.check = 0
return true
}
if(root.letters[index] === "J"){
root.check = 0
return true
}
if(root.letters[index] === "M"){
root.check = 0
return true
}
if(root.letters[index] === "P"){
root.check = 0
return true
}
if(root.letters[index] === "S"){
root.check = 0
return true
}
if(root.letters[index] === "V"){
root.check = 0
return true
}
if(root.letters[index] === "Z"){
root.check = 0
return true
}
else {
root.check = root.check + 1
}
}
Repeater {
model: 26
Text {
anchors.horizontalCenter: parent.left
anchors.horizontalCenterOffset: root.midpoint(index)
anchors.verticalCenter: parent.verticalCenter
text: root.newLetters(index) ? root.letters[index] : (root.check === 0 ? " • " : "")
}
}
}
}
Yes, by changing the root.check variable, the QQmlEngine will re-evaluate all bindings dependent on, including the newLetters function itself, thus leading to a binding loop.
I have been watching your recent questions (wondering what the end-goal is) and I think you should alter your newLetters function, to return the actual wanted text instead of the condition:
function newLetters(index) {
if(index < 24) //below Y, everything is regular
{
if(index % 3 == 0)
return root.letters[index]
if(index % 3 == 1)
return " ° "
}
else if(index == 24) //Y
{
return ""
}
else if(index == 25) //Z
{
return root.letters[index]
}
return ""
}

if the program is maximized and I minimize it and then click back to the program it is in windowed mode?

My program is starting by default in windowed mode.
My problem is if I start it and then press on maximize, then on minimize and then click on it in the taskbar, it is in windowed mode and not maximized.
To be honest I made that whole new window because I like programs that have a nice clear design. I don't know if you can change the top of a normal Window like the icon of the close button or the color of it.
main.qml:
/***********************************************************************************************************/
/*********************************************** S T A R T *************************************************/
/***********************************************************************************************************/
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls.Universal 2.0
import QtQuick.Controls.Imagine 2.3
import QtQuick.Controls.Material 2.0
import QtQuick.Dialogs.qml 1.0
import QtQuick.Extras 1.4
import QtQuick.Templates 2.5
import QtQuick.LocalStorage 2.0
ApplicationWindow {
id: mainWindow
visible: true
width: 900
height: 600
title: qsTr("Test")
color: "#2F3136"
flags: Qt.Window | Qt.FramelessWindowHint
property int _max: 0
property int _min: 0
property int _resizeRightVar: 0
property int _resizeLeftVar: 0
property int _resizeTopVar: 0
property int _resizeBottomVar: 0
property bool _resizeRight: true
property bool _resizeLeft: true
property bool _resizeTop: true
property bool _resizeBottom: true
property int _rRCursor: Qt.SizeHorCursor
property int _rLCursor: Qt.SizeHorCursor
property int _rTCursor: Qt.SizeVerCursor
property int _rBCursor: Qt.SizeVerCursor
property bool _moveWindow: true
property int previousX
property int previousY
///////////////////////////////////////////////////////////
/// TOP RECTANGLE WITH TITLE, MINIMIZE, MAXIMIZE, CLOSE ///
///////////////////////////////////////////////////////////
Rectangle {
id: rectangle
width: parent.width
height: 23
color: "#202225"
anchors.top: parent.top
//////////////////////////////////
/// TITLE IN THE TOP RECTANGLE ///
//////////////////////////////////
Text {
leftPadding: 6
topPadding: 1
opacity: 0.75
text: mainWindow.title
font.pixelSize: 14
font.family: "Dodge"
color: "white"
}
///////////////////////////////////////
/// MOUSE AREA IN THE TOP RECTANGLE ///
///////////////////////////////////////
MouseArea {
anchors.fill: parent
enabled: mainWindow._moveWindow
onPressed: {
previousX = mouseX
previousY = mouseY
}
onMouseXChanged: {
var dx = mouseX - previousX
mainWindow.setX(mainWindow.x + dx)
}
onMouseYChanged: {
var dy = mouseY - previousY
mainWindow.setY(mainWindow.y + dy)
}
onDoubleClicked: {
//mainWindow.visibility = "Maximized"
mainWindow._max ++
if(mainWindow._max == 1){
mainWindow.visibility = "Maximized"
//mainWindow.showMaximized()
mainWindow._moveWindow = false
mainWindow._rRCursor = Qt.ArrowCursor
mainWindow._rLCursor = Qt.ArrowCursor
mainWindow._rTCursor = Qt.ArrowCursor
mainWindow._rBCursor = Qt.ArrowCursor
mainWindow._resizeRight = false
mainWindow._resizeLeft = false
mainWindow._resizeTop = false
mainWindow._resizeBottom = false
} else if(mainWindow._max == 2){
mainWindow.visibility = "Windowed"
//mainWindow.showNormal()
mainWindow._max = 0
mainWindow._moveWindow = true
mainWindow._rRCursor = Qt.SizeHorCursor
mainWindow._rLCursor = Qt.SizeHorCursor
mainWindow._rTCursor = Qt.SizeVerCursor
mainWindow._rBCursor = Qt.SizeVerCursor
mainWindow._resizeRight = true
mainWindow._resizeLeft = true
mainWindow._resizeTop = true
mainWindow._resizeBottom = true
}
}
}
/////////////////////////////////////////
/// CLOSE BUTTON IN THE TOP RECTANGLE ///
/////////////////////////////////////////
Rectangle {
id: close_window
width: 27
height: 22.5
anchors.top: parent.top
anchors.topMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
color: "#202225"
Image {
id: closeImage
source: "close_window.png"
anchors.fill: parent
opacity: 0.6
fillMode: Image.PreserveAspectFit
}
MouseArea {
id: close_windowMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
mainWindow.close()
}
onEntered: {
cursorShape: Qt.PointingHandCursor
close_window.color = "#F04747"
closeImage.opacity = 1
}
onExited: {
cursorShape: Qt.ArrowCursor
close_window.color = "#202225"
closeImage.opacity = 0.6
}
}
}
////////////////////////////////////////////
/// MAXIMIZE BUTTON IN THE TOP RECTANGLE ///
////////////////////////////////////////////
Rectangle {
id: maximize_window
width: 27
height: 22.5
anchors.top: parent.top
anchors.topMargin: 0
anchors.right: parent.right
anchors.rightMargin: 29
color: "#202225"
Image {
id: maximizeImage
source: "maximize_window.png"
anchors.fill: parent
opacity: 0.6
fillMode: Image.PreserveAspectFit
}
MouseArea {
id: maximize_windowMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
mainWindow._max ++
if(mainWindow._max == 1){
mainWindow.visibility = "Maximized"
//mainWindow.showMaximized()
mainWindow._moveWindow = false
mainWindow._rRCursor = Qt.ArrowCursor
mainWindow._rLCursor = Qt.ArrowCursor
mainWindow._rTCursor = Qt.ArrowCursor
mainWindow._rBCursor = Qt.ArrowCursor
mainWindow._resizeRight = false
mainWindow._resizeLeft = false
mainWindow._resizeTop = false
mainWindow._resizeBottom = false
} else if(mainWindow._max == 2){
mainWindow.visibility = "Windowed"
//mainWindow.showNormal()
mainWindow._max = 0
mainWindow._moveWindow = true
mainWindow._rRCursor = Qt.SizeHorCursor
mainWindow._rLCursor = Qt.SizeHorCursor
mainWindow._rTCursor = Qt.SizeVerCursor
mainWindow._rBCursor = Qt.SizeVerCursor
mainWindow._resizeRight = true
mainWindow._resizeLeft = true
mainWindow._resizeTop = true
mainWindow._resizeBottom = true
}
}
onEntered: {
maximize_window.color = "#2B2D30"
maximizeImage.opacity = 1
}
onExited: {
maximize_window.color = "#202225"
maximizeImage.opacity = 0.6
}
}
}
////////////////////////////////////////////
/// MINIMIZE BUTTON IN THE TOP RECTANGLE ///
////////////////////////////////////////////
Rectangle {
id: minimize_window
width: 27
height: 22.5
anchors.top: parent.top
anchors.topMargin: 0
anchors.right: parent.right
anchors.rightMargin: 57
color: "#202225"
Image {
id: minimizeImage
source: "minimize_window.png"
anchors.fill: parent
opacity: 0.6
fillMode: Image.PreserveAspectFit
}
MouseArea {
id: minimize_windowMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
mainWindow._min ++
if(mainWindow._min >= 1){
mainWindow._min = 0
//mainWindow.showMinimized()
mainWindow.visibility = "Minimized"
//if(mainWindow._max == 1){
// mainWindow.showMaximized()
// mainWindow.visibility = "Maximized"
//}
}
}
onEntered: {
minimize_window.color = "#2B2D30"
minimizeImage.opacity = 1
}
onExited: {
minimize_window.color = "#202225"
minimizeImage.opacity = 0.6
}
}
}
}
///////////////////////////
/// RIGHT RESIZE WINDOW ///
///////////////////////////
MouseArea {
id: rightResize
width: 5
enabled: mainWindow._resizeRight
cursorShape: mainWindow._rRCursor
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
}
onEntered: {
if(mainWindow._resizeRight == true){
mainWindow._rRCursor = Qt.SizeHorCursor
} else if(mainWindow._resizeRight == false){
mainWindow._rRCursor = Qt.ArrowCursor
}
}
onPressed: previousX = mouseX
onMouseXChanged: {
var dx = mouseX - previousX
mainWindow.setWidth(parent.width + dx)
}
}
//////////////////////////
/// LEFT RESIZE WINDOW ///
//////////////////////////
MouseArea {
id: leftResize
width: 5
enabled: mainWindow._resizeLeft
cursorShape: mainWindow._rLCursor
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
onEntered: {
if(mainWindow._resizeLeft == true){
mainWindow._rLCursor = Qt.SizeHorCursor
} else if(mainWindow._resizeLeft == false){
mainWindow._rLCursor = Qt.ArrowCursor
}
}
onPressed: previousX = mouseX
onMouseXChanged: {
var dx = mouseX - previousX
mainWindow.setX(mainWindow.x + dx)
mainWindow.setWidth(mainWindow.width - dx)
}
}
/////////////////////////
/// TOP RESIZE WINDOW ///
/////////////////////////
MouseArea {
id: topResize
height: 5
enabled: mainWindow._resizeTop
cursorShape: mainWindow._rTCursor
anchors {
top: parent.top
left: parent.left
right: parent.right
}
onEntered: {
if(mainWindow._resizeTop == true){
mainWindow._rTCursor = Qt.SizeVerCursor
} else if(mainWindow._resizeTop == false){
mainWindow._rTCursor = Qt.ArrowCursor
}
}
onPressed: previousY = mouseY
onMouseYChanged: {
var dy = mouseY - previousY
mainWindow.setY(mainWindow.y + dy)
mainWindow.setHeight(mainWindow.height - dy)
}
}
////////////////////////////
/// BOTTOM RESIZE WINDOW ///
////////////////////////////
MouseArea {
id: bottomResize
height: 5
enabled: mainWindow._resizeBottom
cursorShape: mainWindow._rBCursor
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
onEntered: {
if(mainWindow._resizeBottom == true){
mainWindow._rBCursor = Qt.SizeVerCursor
} else if(mainWindow._resizeBottom == false){
mainWindow._rBCursor = Qt.ArrowCursor
}
}
onPressed: previousY = mouseY
onMouseYChanged: {
var dy = mouseY - previousY
//mainWindow.setY(mainWindow.y + dy)
mainWindow.setHeight(mainWindow.height + dy)
}
}
////////////////////////////
/// TEXT INPUT IN MIDDLE ///
////////////////////////////
Rectangle{
id: textInputBG
}
}
/***********************************************************************************************************/
/************************************************** E N D **************************************************/
/***********************************************************************************************************/
Here's a hacky way to solve this:
Add property int saved_state to your ApplicationWindow
Then add the following code:
onVisibilityChanged: {
if(saved_state === 4 && visibility === 2) {
showMaximized()
saved_state = -1
}
}
Then isnide your MouseArea minimize_sindowMouseArea add the following line:
saved_state = mainWindow.visibility //NEW LINE
mainWindow.visibility = "Minimized" //Existing line
Explanation: Save the state of your window right before you minimize, then restore it to the save_state when restored.
I don't know of any cleaner way, but that should do it :)

QML: How to re-order repeater items with drag and drop? Somewhat working code inside

I'm trying to reorder Repeater items using drag & drop. I'm using a Repeater due to dynamically loaded data. Below is what I have so far using a simple sqlite database with sample data added. What I'm hoping for is to get the re-order set up so when the user releases the dragged object, the database is updated to reflect the new order. The "Change" button reorders the elements as I want, I just can't get it to work property with drag and drop.
The loadData() function simply creates the table and inserts sample data. The "Change" button won't be in the final code, I just wanted to use it to get the re-ordering code to work.
import QtQuick.Controls 2.2 as QC2
import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.LocalStorage 2.0
Window {
id: root
width: 320
height: 480
property var db
property int rowHeight: 90
Component.onCompleted: {
db = LocalStorage.openDatabaseSync("test_db", "1.0", "Database", 100000)
loadData()
}
property var sql_data: []
function loadData() {
var sql, rs, len, i
db.transaction(function(tx) {
tx.executeSql("DROP TABLE IF EXISTS tbl")
tx.executeSql("CREATE TABLE IF NOT EXISTS tbl (
rowId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, listOrder INTEGER NOT NULL)")
len = 5
for (i = 0; i < len; i++)
tx.executeSql("INSERT INTO tbl (name, listOrder) VALUES ('Item " + i + "', " + i + ")")
rs = tx.executeSql("SELECT name, listOrder FROM tbl ORDER BY listOrder ASC")
})
len = rs.rows.length
sql_data = new Array(len)
for (i = 0; i < len; i++) {
sql_data[i] = {"name": "", "order": ""}
sql_data[i]["name"] = rs.rows.item(i).name
sql_data[i]["order"] = rs.rows.item(i).listOrder
}
repeater.model = sql_data
}
Flickable {
id: mainFlick
anchors.fill: parent
contentHeight: mainColumn.height
Column {
id: mainColumn
width: parent.width
QC2.Button {
text: "Change"
onClicked: {
var p1, p2
var d1 = new Date().getTime()
var movedEle = 3
var newPos = 1
var ele1 = repeater.itemAt(movedEle)
var ele2 = repeater.itemAt(newPos)
ele1.y = ele2.y
root.sql_data[movedEle]["order"] = root.sql_data[newPos]["order"]
if (newPos < movedEle) {
p1 = newPos
p2 = movedEle
} else {
p1 = movedEle
p2 = newPos
}
root.db.transaction(function(tx) {
var sql = "UPDATE tbl SET listOrder = " + root.sql_data[p1]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[p2]["order"])
for (var i = p1; i < p2; i++) {
if (i !== movedEle) {
repeater.itemAt(i).y = repeater.itemAt(i).y + rowHeight
root.sql_data[i]["order"] += 1
sql = "UPDATE tbl SET listOrder = " + root.sql_data[i]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[i]["order"] - 1)
tx.executeSql(sql)
}
}
})
sql_data = sql_data.sort(function(a,b) {
return a["order"] - b["order"]
})
repeater.model = sql_data
var d2 = new Date().getTime()
console.debug("Seconds: " + (d2 - d1) / 1000)
}
}
Repeater {
id: repeater
width: parent.width
model: []
delegate: Column {
id: repeaterItem
width: parent.width - 20
height: rowHeight
anchors.horizontalCenter: parent.horizontalCenter
property int pos: index
DropArea {
id: dragTarget
width: parent.width
height: 20
Rectangle {
id: dropRect
anchors.fill: parent
color: "green"
states: [
State {
when: dragTarget.containsDrag
PropertyChanges {
target: dropRect
color: "red"
}
}
]
}
}
Row {
id: contentRow
width: parent.width
height: parent.height
z: 1
Column {
id: itemColumn
width: parent.width - 70
Text {
text: "Name: " + modelData["name"]
}
Text {
text: "Order: " + modelData["order"]
}
Text {
text: "Repeater Index: " + index
}
} // itemColumn
MouseArea {
id: dragArea
width: 40
height: itemColumn.height
drag.axis: Drag.YAxis
drag.target: dragRect
onReleased: parent = ((dragRect.Drag.target !== null) ? dragRect.Drag.target : root)
Rectangle {
Component.onCompleted: console.debug(dragRect.Drag.target)
id: dragRect
width: 40
height: itemColumn.height
color: "grey"
Drag.active: dragArea.drag.active
Drag.source: dragArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
states : State {
when: dragArea.drag.active
ParentChange { target: dragRect; parent: root }
AnchorChanges {
target: dragRect
anchors.verticalCenter: undefined
anchors.horizontalCenter: undefined
}
}
}
}
} // contentRow
Behavior on y {
PropertyAnimation {
duration: 200
easing.type: Easing.Linear
}
}
} // repeaterItem
} // repeater
} // mainColumn
} // mainFlick
} // id
Much of the drag and drop code came from the Qt Examples site.
Here's what my problems are:
The MouseArea used for dragging the rectangles doesn't seem to change locations. Once a rectangle is actually moved, it stays in its new location but if I want to move it again, I have to click and drag where it was originally when the app loads.
If I switch the target of the Drag Area from the child rectangle to the entire row (repeaterItem), everything moves properly but will no longer interact with the Drop areas.
I think I can get the index of the new location after a row has been dragged simply by getting the y-value of the Drop Area. Would there be a better way to do this?
Since the "Change" button already re-orders the row elements, I only need help getting the rows to properly interact with the Drop Areas and then get the y-position of the Drop Area when the dragged item is released.
It may not be the most efficient out there but it's a good start for anybody looking to do something similar.
import QtQuick.Controls 2.2 as QC2
import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.LocalStorage 2.0
Window {
id: root
width: 320
height: 580
property var db
property int rowHeight: 90
property int len
Component.onCompleted: {
db = LocalStorage.openDatabaseSync("test_db", "1.0", "Database", 100000)
loadData()
}
property var sql_data: []
function loadData() {
var sql, rs, i
db.transaction(function(tx) {
tx.executeSql("DROP TABLE IF EXISTS tbl")
tx.executeSql("CREATE TABLE IF NOT EXISTS tbl (
rowId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, listOrder INTEGER NOT NULL)")
len = 15
for (i = 0; i < len; i++)
tx.executeSql("INSERT INTO tbl (name, listOrder) VALUES ('Item " + i + "', " + i + ")")
rs = tx.executeSql("SELECT name, listOrder FROM tbl ORDER BY listOrder ASC")
})
len = rs.rows.length
sql_data = new Array(len)
for (i = 0; i < len; i++) {
sql_data[i] = {"name": "", "order": ""}
sql_data[i]["name"] = rs.rows.item(i).name
sql_data[i]["order"] = rs.rows.item(i).listOrder
}
repeater.model = sql_data
}
Flickable {
id: mainFlick
anchors.fill: parent
contentHeight: mainColumn.height
rebound: Transition {
NumberAnimation {
properties: "y"
duration: 500
easing.type: Easing.OutCirc
}
}
Column {
id: mainColumn
width: parent.width
function moveEle(start, end, f) {
if (f === false) {
var p1, p2, ele1, ele2
var c = false
var d1 = new Date().getTime()
ele1 = repeater.itemAt(start)
console.debug(f)
ele2 = repeater.itemAt(end)
root.sql_data[start]["order"] = root.sql_data[end]["order"]
if (end < start) {
p1 = end
p2 = start
} else {
p1 = start
p2 = end
c = true
}
root.db.transaction(function(tx) {
var sql = "UPDATE tbl SET listOrder = " + root.sql_data[p1]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[p2]["order"])
for (var i = p1; i < p2; i++) {
if (c === false) {
if (i !== start) {
root.sql_data[i]["order"]++
sql = "UPDATE tbl SET listOrder = " + root.sql_data[i]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[i]["order"] - 1)
tx.executeSql(sql)
}
} else {
root.sql_data[i]["order"]--
sql = "UPDATE tbl SET listOrder = " + root.sql_data[i]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[i]["order"] - 1)
tx.executeSql(sql)
}
}
})
} else if (f === true) {
c = false
d1 = new Date().getTime()
ele1 = repeater.itemAt(start)
console.debug(f)
end--
ele2 = repeater.itemAt(end)
root.sql_data[start]["order"] = root.sql_data[end]["order"]
p1 = start
p2 = end
c = true
root.db.transaction(function(tx) {
var sql = "UPDATE tbl SET listOrder = " + root.sql_data[p1]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[p2]["order"])
tx.executeSql(sql)
for (var i = p1; i <= p2; i++) {
if (i !== start) {
root.sql_data[i]["order"]--
sql = "UPDATE tbl SET listOrder = " + root.sql_data[i]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[i]["order"] - 1)
tx.executeSql(sql)
}
}
})
}
sql_data = sql_data.sort(function(a,b) {
return a["order"] - b["order"]
})
repeater.model = sql_data
var d2 = new Date().getTime()
console.debug("Seconds: " + (d2 - d1) / 1000)
}
Repeater {
id: repeater
width: parent.width
model: []
delegate: Column {
id: repeaterItem
width: parent.width
height: rowHeight
anchors.horizontalCenter: parent.horizontalCenter
z: dragArea.drag.active ? 2 : 1
property int pos: index
DropArea {
id: dragTarget
width: parent.width
height: 15
property int ind: index
onEntered: {
drag.source.ind = index
drag.source.f = false
if (drag.source.ind !== drag.source.oldInd && drag.source.ind !== drag.source.oldInd + 1)
drag.source.caught = true
}
onExited: drag.source.caught = false
Rectangle {
id: dropRect
anchors.fill: parent
z: 0
states: [
State {
when: dragTarget.containsDrag
PropertyChanges {
target: dropRect
color: "grey"
}
PropertyChanges {
target: dropRectLine
visible: false
}
}
]
Rectangle {
id: dropRectLine
width: parent.width
height: 1
anchors.verticalCenter: parent.verticalCenter
color: "black"
}
}
}
Row {
id: contentRow
width: parent.width
height: parent.height
Drag.active: dragArea.drag.active
Drag.source: dragArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
Column {
id: itemColumn
width: parent.width - 70
Text {
text: "Name: " + modelData["name"]
}
Text {
text: "Order: " + modelData["order"]
}
Text {
text: "Repeater Index: " + index
}
} // itemColumn
MouseArea {
id: dragArea
width: 40 //repeater.width
height: itemColumn.height
drag.axis: Drag.YAxis
drag.target: contentRow
property point beginDrag
property point dropTarget
property bool caught: false
property int ind
property int oldInd: index
property bool f
onPressed: {
dragArea.beginDrag = Qt.point(contentRow.x, contentRow.y);
}
onReleased: {
if (dragArea.caught) {
dropRectFinal.color = "white"
dropRectLineFinal.visible = true
mainColumn.moveEle(index,dragArea.ind, dragArea.f)
} else {
backAnimY.from = contentRow.y;
backAnimY.to = beginDrag.y;
backAnim.start()
}
}
Rectangle {
id: dragRect
width: 40
height: itemColumn.height
color: "grey"
}
} // contentRow
} // dragArea
ParallelAnimation {
id: backAnim
SpringAnimation { id: backAnimY; target: contentRow; property: "y"; duration: 300; spring: 2; damping: 0.2 }
}
} // repeaterItem
} // repeater
DropArea {
id: dragTargetFinal
width: parent.width
height: 15
property int ind: root.len
onEntered: {
drag.source.ind = ind
drag.source.f = true
if (drag.source.ind !== drag.source.oldInd && drag.source.ind !== drag.source.oldInd + 1)
drag.source.caught = true
}
onExited: drag.source.caught = false
Rectangle {
id: dropRectFinal
anchors.fill: parent
states: [
State {
when: dragTargetFinal.containsDrag
PropertyChanges {
target: dropRectFinal
color: "grey"
}
PropertyChanges {
target: dropRectLineFinal
visible: false
}
}
]
Rectangle {
id: dropRectLineFinal
width: parent.width
height: 1
anchors.verticalCenter: parent.verticalCenter
color: "black"
}
}
}
} // mainColumn
QC2.ScrollBar.vertical: QC2.ScrollBar { active: scrollAnim.running ? true : false }
} // mainFlick
DropArea {
id: scrollUp
width: parent.width
height: 50
anchors.top: parent.top
visible: {
var visible = false
if (mainFlick.contentY > 0)
visible = true
visible
}
onEntered: {
scrollAnim.from = mainFlick.contentY
scrollAnim.to = 0
scrollAnim.start()
}
onExited: scrollAnim.stop()
}
DropArea {
id: scrollDown
width: parent.width
height: 50
anchors.bottom: parent.bottom
visible: {
var visible = false
if (mainFlick.contentY < mainColumn.height - Window.height)
visible = true
visible
}
onEntered: {
scrollAnim.from = mainFlick.contentY
scrollAnim.to = mainColumn.height - Window.height
scrollAnim.start()
}
onExited: scrollAnim.stop()
}
SmoothedAnimation {
id: scrollAnim
target: mainFlick
property: "contentY"
velocity: 400
loops: 1
maximumEasingTime: 10
}
} // root

How disable drag of points in the x axis on chartview

I have a qtchart with a lineseries with 5 points on the graph. I can drag these points on in both x and y axis inside min and max of axisX and axisY.
How can disable drag of points in the x axis? so it is possible to drag points vertically on the chart. Here is my code:
ChartView {
id: chart
property var selectedPoint: undefined
anchors.fill: parent
antialiasing: true
property real toleranceX: 0.05
property real toleranceY: 0.05
ValueAxis {
id: axisX
min: 0
max: 5
}
ValueAxis {
id: axisY
min: -10
max: 10
}
LineSeries {
id: series1
axisX: axisX
axisY: axisY
pointsVisible: true
}
MouseArea {
anchors.fill: parent
onPressed:
{
var cp = chart.mapToValue(Qt.point(mouse.x,mouse.y));
for(var i = 0;i < series1.count;i ++)
{
var p = series1.at(i);
if(Math.abs(cp.x - p.x) <= chart.toleranceX && Math.abs(cp.y - p.y) <= chart.toleranceY)
{
chart.selectedPoint = p;
break;
}
}
}
onPositionChanged: {
if(chart.selectedPoint != undefined) {
var p = Qt.point(mouse.x, mouse.y);
var cp = chart.mapToValue(p);
if(cp.x >= axisX.min && cp.x <= axisX.max && cp.y >= axisY.min && cp.y <= axisY.max) {
series1.replace(chart.selectedPoint.x, chart.selectedPoint.y, cp.x, cp.y);
chart.selectedPoint = cp;
}
}
}
onReleased: {
chart.selectedPoint = undefined;
}
}
}
// Add data to the series
Component.onCompleted: {
for (var i = 0; i <= 5; i++) {
series1.append(i, Math.random());
}
}
So far I could disable drag on x axis for one point in this case point on 3 by changing onPositionChanged to following code but there are two problems, first this is just one point, second, it does not disable the drag completely instead the point can be dragged on x axis between 2 to 4 but it cannot be dragged outside this range.
onPositionChanged: {
if(chart.selectedPoint != undefined) {
var p = Qt.point(mouse.x, mouse.y);
var cp = chart.mapToValue(p);
if(cp.x >= axisX.min && cp.x <= axisX.max && cp.y >= axisY.min && cp.y <= axisY.max) {
if (Math.round(cp.x) == 3){
series1.replace(chart.selectedPoint.x, chart.selectedPoint.y, cp.x, cp.y);
chart.selectedPoint = cp;
}
}
}
}

QtQuick 2 - Side Panel example

Can someone help me to understand behaviour of this code? I wrote simple example of side panel menu (just template now) and there is problem to use onMenuVisibleChanged slot (but all I need actually works, just want to understand why another way don't). Logically it should change x of panel rectangle to specified values, but it didn't. Please help me ot understand correct approach for this thing.
main.qml
import QtQuick 2.3
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
import QtQuick.Dialogs 1.2
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true
menuBar: MenuBar{
Menu{
title: "File"
MenuItem{
text: "Exit"
onTriggered: Qt.quit()
}
}
}
SidePane{
id: sidePane
menuWidth: 350
z: 2
}
}
SidePane.qml
import QtQuick 2.3
import QtQuick.Window 2.2
Item {
id: screenItem
anchors.fill: parent
function show() {
rect.x = 0
menuVisible = true
}
function hide() {
rect.x = -rect.width
menuVisible = false
}
property int animationDuration: 200
property bool menuVisible: false
property int dragThreshold: 120
property int menuWidth: 300
Rectangle {
id: rect
width: menuWidth
x: -rect.width
height: Screen.height
color: "lightsteelblue"
Drag.active: menuDragArea.drag.active
Behavior on x {
NumberAnimation {
duration: animationDuration
easing.type: Easing.InOutQuad
}
}
MouseArea {
property int dragX: 0
property int startX: 0
property int diffX: 0
id: menuDragArea
hoverEnabled: true
height: rect.height
width: rect.width + 40
anchors.left: rect.left
drag.target: rect
drag.axis: Drag.XAxis
drag.minimumX: -rect.width
drag.maximumX: 0
onPressed: startX = rect.x + rect.width
onReleased: {
dragX = rect.x + rect.width
diffX = Math.abs(startX - dragX)
if ((diffX > dragThreshold) && (startX == 0)){
rect.x = 0
menuVisible = true
} else if ((diffX < dragThreshold) && (startX == 0)){
rect.x = -rect.width
menuVisible = false
}
if ((diffX > dragThreshold) && (startX == rect.width)){
rect.x = -rect.width
menuVisible = false
} else if ((diffX < dragThreshold) && (startX == rect.width)){
rect.x = 0
menuVisible = true
}
}
}
}
Rectangle{
id: shadowRect
anchors.left: rect.right
anchors.right: screenItem.right
opacity: (rect.x + rect.width) / (rect.width * 2.2)
color: "black"
height: screenItem.height
MouseArea{
id: shadowArea
anchors.fill: shadowRect
visible: menuVisible ? true : false
hoverEnabled: true
onClicked: {
if (menuVisible == true){
rect.x = -rect.width
menuVisible = false
}
}
}
Rectangle{
id: shadowRect2
color: "black"
anchors.left: parent.left
width: 5
opacity: (rect.x + rect.width) / (rect.width * 2)
height: screenItem.height
}
Rectangle{
id: shadowRect3
color: "black"
anchors.left: parent.left
width: 3
opacity: (rect.x + rect.width) / (rect.width * 1.9)
height: screenItem.height
}
}
onMenuVisibleChanged: menuVisible ? rect.x = 0 : rect.x = -rect.width
}
Appreciate any help. Just a QML beginner. The most purpose is to use this panel in android application. Feel free to use this code if you want.
The property drag make the x value draggable
if you implement it by mouseX onPressed/onReleased, rect.x will be 0 or -rect.width onMenuVisibleChanged
MouseArea {
property int dragX: 0
property int startX: 0
property int diffX: 0
id: menuDragArea
hoverEnabled: true
height: rect.height
width: rect.width + 40
anchors.left: rect.left
onPressed: {
startX = mouseX;
console.log(rect.x)
}
onReleased: {
diffX = Math.abs(startX - mouseX)
console.log("diff:"+diffX)
if ((diffX > dragThreshold) && (mouseX > startX) && (rect.x !=0 )){
rect.x = 0
menuVisible = true
} else if ((diffX > dragThreshold) && (mouseX < startX) && (rect.x == 0)){
rect.x = -rect.width
menuVisible = false
}
}
}
with visual drag ,sorry it's ugly:
MouseArea {
property int startX: 0
property int diffX: 0
property int startRectX: 0
property bool isPressed: false
id: menuDragArea
hoverEnabled: true
height: rect.height
width: rect.width + 40
anchors.left: rect.left
onPressed: {
startX = mouseX;
startRectX = rect.x
isPressed = true
console.log(rect.x)}
onMouseXChanged: {
if(isPressed)
rect.x = (mouseX>rect.width)? 0 : (mouseX-rect.width)
}
onReleased: {
isPressed = false
diffX = Math.abs(startX - mouseX)
console.log("diff:"+diffX)
if ((mouseX >= startX) && (startRectX == -rect.width )){
if(diffX > dragThreshold)
{
rect.x = 0
menuVisible = true
}
else
rect.x = startRectX
} else if ((mouseX <= startX) && (startRectX == 0)){
if(diffX > dragThreshold)
{
rect.x = -rect.width
menuVisible = false
}
else
rect.x = startRectX
}
else
rect.x = startRectX
}
}

Resources