Qt 创建一个对触摸事件敏感的 QML 滑块

Posted

技术标签:

【中文标题】Qt 创建一个对触摸事件敏感的 QML 滑块【英文标题】:Qt Create a QML Slider Sensitive to Touch Events 【发布时间】:2017-01-01 21:53:10 【问题描述】:

我正在为触摸屏创建一个游戏,该游戏需要 2-4 名玩家,每个玩家都可以使用一对滑块控件。问题是 QML Slider 控件将触摸作为鼠标事件响应并获取焦点。然后一次只有一个玩家可以访问一个控件。我需要多个滑块来同时响应触摸事件。我的问题是如何做到这一点?

在各种堆栈溢出帖子的帮助下,我已经能够创建自己的答案,到目前为止似乎有效。我在答案部分详细说明了下面的答案,以免像我这样的其他新手麻烦。

【问题讨论】:

仅供参考,Qt Quick Controls 2 在 Qt 5.9 中获得了多点触控支持,将于春季晚些时候发布。 那太好了。 对于它的价值,这是一个完全有效的问题。我不明白为什么这个问题被一些人否决了。回想起来,为 Qt Quick Controls 2 添加适当的触摸支持应该有更高的优先级,但不知何故,它被埋在无尽的待办事项列表中的其他事情之下...... 【参考方案1】:

这个问题有一个纯qml的解决方案。我的第一个答案(在这个线程的其他地方)中的 TouchSlider C++ 对象是不必要的。这里我将代码修改为 TouchSlider qml 代码,以消除对 touchslider(TouchSlider C++ 对象)的引用。

TouchPoint.qml:

import QtQuick 2.0
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2

Item 
    property string sliderTitle

    property real sliderMin
    property real sliderMax
    property real sliderVal


    ColumnLayout
        id: column1
        Label 
            text: qsTr(sliderTitle)
            font.pointSize: 10

        
        Slider 
            id: touchSlider1

            minimumValue: sliderMin
            maximumValue: sliderMax

            orientation: Qt.Vertical
            value: sliderVal
            onValueChanged: function()
                sliderVal = Math.round(this.value);
                labelSliderValue.text = qsTr(JSON.stringify(sliderVal));
            
        

        Label 
             id: labelSliderValue
             text: qsTr(JSON.stringify(sliderVal))
             font.pointSize: 10
        
        function sliderSetValueFromTouch(position)
            // Assume qs a vertical slider
            var minvalue = touchSlider1.minimumValue;

            var maxvalue = touchSlider1.maximumValue;
            // Since this is a vertical slider, by assumption, get the height
            var height = touchSlider1.height;

            // Compute the new value based on position coordinate
            var newvalue = (height-position)/height * (maxvalue-minvalue);
            if (newvalue<minvalue) newvalue = minvalue;
            if (newvalue>maxvalue) newvalue = maxvalue;
            //qDebug() << newvalue;

            // Set the value of the slider
            touchSlider1.value = newvalue;
        

        MultiPointTouchArea
            anchors.fill: touchSlider1

            touchPoints: [
                TouchPoint 
                    id: point1
                    onPressedChanged: function()
                        if(pressed)
                            //console.log("pressed");
                            //console.log(touchslider.testStringReturn());
                            //touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
                            column1.sliderSetValueFromTouch(point1.y);
                        
                    

                
            ]
            onTouchUpdated: function()
                //touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
                column1.sliderSetValueFromTouch(point1.y);
            

        
    

touchslider.h 和 touchslider.cpp 文件没有任何价值。

【讨论】:

【参考方案2】:

我找不到解决问题的纯 QML 方法,但我想尽量减少 C++ 的使用。使用 C++,我创建了一个对象 TouchSlider 并将其添加到我的 qml 场景中。 TouchSlider 对象有一个简单的函数来根据位置参数更新垂直滑块的值。然后在 QML 代码中,我在常规滑块顶部添加了一个 MultiPointTouchArea,并通过调用 C++ 函数来响应触摸事件。

这是我名为 SliderPair 的项目的所有文件。

SliderPair.pro:

QT += qml quick widgets
QT += quickcontrols2
QT += core
CONFIG += c++11
SOURCES += main.cpp \
    touchslider.cpp
RESOURCES += \
    qml.qrc

# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH += qml

# Default rules for deployment.
qnx: target.path = /tmp/$$TARGET/bin
else: unix:!android: target.path = /opt/$$TARGET/bin
!isEmpty(target.path): INSTALLS += target

HEADERS += \
    touchslider.h

DISTFILES +=

main.cpp:

#include <QApplication>
#include <QQmlApplicationEngine>
// add following includes for exposing new class TouchSlider to QML
#include <QQmlEngine>
#include <QQmlContext>
#include "touchslider.h"

int main(int argc, char *argv[])

    QApplication app(argc, argv);

    //Create an object of type TouchSlider
    //When a scoped pointer goes out of scope the object is deleted from memory. Good housekeeping:
    QScopedPointer<TouchSlider> touchslider (new TouchSlider);

    QQmlApplicationEngine engine;
    engine.addImportPath(QStringLiteral("qml"));
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    //QML can now refer to the TouchSlider object using the handle "touchslider":
    engine.rootContext()->setContextProperty("touchslider",touchslider.data());

    return app.exec();

touchslider.h:

#ifndef TOUCHSLIDER_H
#define TOUCHSLIDER_H

#include <QObject>
#include <QDebug>
#include <QtQuickControls2>

class TouchSlider : public QObject

    Q_OBJECT
public:
    explicit TouchSlider(QObject *parent = 0);

    //call Q_INVOKABLE macro to set up functions for QML
    Q_INVOKABLE void testDebug();          //hello world from C++
    Q_INVOKABLE QString testStringReturn(); //hello world to javascript
    Q_INVOKABLE void sliderSetValueFromTouch(QQuickItem *qs,int position );//use touch event to set slider value
signals:

public slots:
;


#endif // TOUCHSLIDER_H

touchslider.cpp:

#include "touchslider.h"

TouchSlider::TouchSlider(QObject *parent) : QObject(parent)




void TouchSlider::testDebug()

   qDebug() << "Hello from C++";


QString TouchSlider::testStringReturn()

    QString message = "Hi from C++";
    return message;


void TouchSlider::sliderSetValueFromTouch(QQuickItem *qs, int position)

    // Assume qs a vertical slider
    // Get its properties (its slider properties are accessible even though it is declared as QQuickItem)
    // minimumValue and maximumValue are of type QVariant so we need to cast them as double
    double minvalue = qs->property("minimumValue").value<double>();
    double maxvalue = qs->property("maximumValue").value<double>();
    // Since this is a vertical slider, by assumption, get the height
    double height = qs->property("height").value<double>();

    // Compute the new value based on position coordinate
    double newvalue = (height-position)/height * (maxvalue-minvalue);
    if (newvalue<minvalue) newvalue = minvalue;
    if (newvalue>maxvalue) newvalue = maxvalue;
    //qDebug() << newvalue;

    // Set the value of the slider
    qs->setProperty("value",newvalue);

TouchSlider.qml:

import QtQuick 2.0
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2

Item 
    property string sliderTitle

    property real sliderMin
    property real sliderMax
    property real sliderVal


    ColumnLayout
        Label 
            text: qsTr(sliderTitle)
            font.pointSize: 10

        
        Slider 
            id: touchSlider1


            minimumValue: sliderMin
            maximumValue: sliderMax

            orientation: Qt.Vertical
            value: sliderVal
            onValueChanged: function()
                sliderVal = Math.round(this.value);
                labelSliderValue.text = qsTr(JSON.stringify(sliderVal));
            
        

        Label 
            id: labelSliderValue
            text: qsTr(JSON.stringify(sliderVal))
            font.pointSize: 10
        
        MultiPointTouchArea
            anchors.fill: touchSlider1
            touchPoints: [
                TouchPoint 
                    id: point1
                    onPressedChanged: function()
                        if(pressed)
                            //console.log("pressed");
                            //console.log(touchslider.testStringReturn());
                            touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);

                        
                    

                
            ]
            onTouchUpdated: function()
                touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
            
        
    

PlayerControls.qml:

import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2


Item 
    // These properties act as constants, useable outside this QML file
    property string playerName
    property real priceMin
    property real priceMax
    property real qualityMin
    property real qualityMax
    property real priceValue
    property real qualityValue
    property int sliderWidth


    ColumnLayout
        id: columnLayout1
        width: 640
        height: 480
        Layout.minimumWidth: 640
        Layout.fillWidth: true
        anchors.fill: parent
        spacing: 10.2

        Label 
            Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
            id: labelPlayer1
            text: qsTr(playerName)
            font.pointSize: 10
        
        RowLayout
            ColumnLayout
                Label 
                    text: qsTr("")
                    font.pointSize: 10
                    width: 50
                
            
            TouchSlider 
                width: sliderWidth

                sliderTitle: "Price"
                sliderMin: priceMin
                sliderMax: priceMax
                sliderVal: priceValue

            
            TouchSlider 
                width: sliderWidth

                sliderTitle: "Quality"
                sliderMin: qualityMin
                sliderMax: qualityMax
                sliderVal: qualityValue
            
        

    
 

main.qml:

import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0

ApplicationWindow 
    visible: true
    width: 640
    height: 480
    title: qsTr("SliderPair Test")
    Item 
        PlayerControls
            playerName: "Player 1"
            priceMin: 0
            priceMax: 200
            priceValue: 100
            qualityMin: 0
            qualityMax: 50
            qualityValue: 25
            sliderWidth: 200
        
    


结果应如下所示:

在像 Surface Pro 这样的触摸屏上,我可以用两根手指同时控制每个滑块。由于 Windows 最多支持 10 个同时触摸,这意味着我可以毫无问题地拥有 2-4 个玩家。我们拭目以待。

【讨论】:

以上是关于Qt 创建一个对触摸事件敏感的 QML 滑块的主要内容,如果未能解决你的问题,请参考以下文章

通过 QWidget 类在 QML 中的事件处理程序

UISlider 释放触摸时过于敏感

如何在黑莓10中处理自定义控件的触摸事件

我需要一个滑块事件,只有当用户结束触摸控件时它才会触发

HarmonyOS - 基于ArkUI (JS) 实现图片旋转验证

Qt 5.5 - 仅在初始(第一个)窗口中工作的触摸屏事件