I want to write simple Qt Quick app with draggable QQuickItems. The items are well draggeble because of embedded MouseArea in the items. But a problem is that mouse events are not fired into C++ code in virtual overloaded functions. How to solve this problem or maybe there are some examples that I didn't find?
The QML file:
import QtQuick 2.0
import SimpleMaterial 1.0
Rectangle {
width: 320
height: 480
color: "black"
SimpleMaterialItem {
width: parent.width;
height: parent.height / 3;
color: "steelblue"
MouseArea {
anchors.fill: parent
width: 64
height: 64
drag.target: parent
drag.axis: Drag.XandYAxis
}
}
}
The C++ class:
class Item : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
public:
Item()
{
setFlag(ItemHasContents, true);
setFlag(ItemAcceptsDrops, true);
setFlag(ItemAcceptsInputMethod, true);
setAcceptedMouseButtons(Qt::AllButtons);
}
void mousePressEvent(QMouseEvent * event)
{
qDebug("Press"); // NOT CALLED!
}
public:
QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *)
{
...
}
};
If MouseArea handles mouse event it doesn't pass event to its parent.
You need:
onPressed: {
mouse.accepted = false;
}
in mouse area to let the SimpleMaterialItem handle onPressed event.
Related
I have a C++ model derived from QAbstractListModel which I am using in a QML ListView
ListView {
id: listView
populate: Transition {
id: addTrans
SequentialAnimation {
PauseAnimation {
duration: (addTrans.ViewTransition.index -
addTrans.ViewTransition.targetIndexes[0]) * 100
}
NumberAnimation { property: "scale"; from: 0; to: 1 }
}
}
anchors.fill: parent
delegate: listDelegate
model: newsModel
}
I tried to add an animation for when the list is populated, but it doesn't work. So what I did was I tried to change populate to add and the animation is triggered, however the problem is that that each item is already in the list when the animation starts: in other words, instead of having an empty list to which items are added, I have a full list of elements that are then individually animated. I have tried also following this suggestion but it doesn't work.
EDIT: as requested I am providing a minimal working example to reproduce my problem.
The problem can be reproduced with the following:
#include <QObject>
#include <QQmlObjectListModel.h>
class Item : public QObject{
Q_OBJECT
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
public:
Item(QObject* parent = nullptr): QObject(parent)
{
}
QString name() const{
return mName;
}
void setName(QString const &name){
if(mName != name){
mName = name;
emit nameChanged();
}
}
signals:
void nameChanged();
private:
QString mName;
};
class DataManager : public QObject{
Q_OBJECT
Q_PROPERTY(QQmlObjectListModel<Item>* itemsModel READ itemsModel CONSTANT)
public:
DataManager(){
mItemsModel = new QQmlObjectListModel<Item>(this);
qRegisterMetaType<QQmlObjectListModel<Item>*>("QQmlObjectListModel<Item>*");
// I have also tried to add items from here, but the result is even worse
// for(int i = 0; i < 20; i++){
// addItem(QString::number(i));
// }
}
QQmlObjectListModel<Item>* itemsModel(){
return mItemsModel;
}
Q_INVOKABLE void addItem(QString const &name){
Item* item = new Item(mItemsModel);
item->setName(name);
mItemsModel->append(item);
}
private:
QQmlObjectListModel<Item>* mItemsModel;
};
Then in qml:
import QtQuick 2.12
import QtQuick.Controls 2.5
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Scroll")
ScrollView {
anchors.fill: parent
ListView {
width: parent.width
// dataManager is a context property set via c++
model: dataManager.itemsModel
populate: Transition {
id: popTrans
SequentialAnimation {
PauseAnimation {
duration: (popTrans.ViewTransition.index -
popTrans.ViewTransition.targetIndexes[0]) * 100
}
NumberAnimation { property: "scale"; from: 0; to: 1 }
}
}
add: Transition {
id: addTrans
SequentialAnimation {
PauseAnimation {
duration: (addTrans.ViewTransition.index -
addTrans.ViewTransition.targetIndexes[0]) * 100
}
NumberAnimation { property: "scale"; from: 0; to: 1 }
}
}
delegate: ItemDelegate {
text: "Item" + name
width: parent.width
}
}
}
Component.onCompleted: {
var p;
for(p=0; p < 20; p++){
dataManager.addItem(p)
}
}
}
The QQmlObjectListModel is a helper class which I borrowed from here
I am not sure what you want exactly, but I hope my code help you.
populate: Transition {
id: addTrans
SequentialAnimation {
PropertyAction {
property: "visible"
value: false
}
PauseAnimation {
duration: (addTrans.ViewTransition.index -
addTrans.ViewTransition.targetIndexes[0]) * 100
}
PropertyAction {
property: "visible"
value: true
}
NumberAnimation { property: "scale"; from: 0; to: 1 }
}
}
This is my custom control. It draws background. I want it to occupy the whole header. But none of methods works, expect setting width and height explicitly, using some numbers. Even parent.width and parent.height does not work.
Control - TabViewHeaderBg.
Code:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import MyControls 1.0
Item
{
property int preInfoColumnWidth
property int nameColumnWidth
property int progressColumnWidth
property int sizeColumnWidth
property int columnSpacing
height: 40
TabViewHeaderBg
{
anchors.fill: parent
}
Row
{
spacing: columnSpacing
Row
{
width: preInfoColumnWidth
CheckBox
{
}
}
Text
{
text: "Name"
width: nameColumnWidth
anchors.verticalCenter: parent.verticalCenter
}
Text
{
text: "Status"
width: progressColumnWidth
anchors.verticalCenter: parent.verticalCenter
}
Text
{
text: "Size"
width: sizeColumnWidth
anchors.verticalCenter: parent.verticalCenter
}
}
}
This whole control is used as the header control in ListView.
Addition #1.
TabViewHeaderBgQc code:
class TabViewHeaderBgQc :
public QQuickPaintedItem
{
Q_OBJECT
public:
TabViewHeaderBgQc(
QQuickItem *parent = 0);
void paint(QPainter *painter) override;
signals:
public slots:
};
TabViewHeaderBgQc::TabViewHeaderBgQc(
QQuickItem *parent) :
QQuickPaintedItem(parent)
{
}
void TabViewHeaderBgQc::paint(QPainter *painter)
{
QPen pen(QColor(229,229,229), 1);
painter->setPen(pen);
painter->drawRect(boundingRect());
}
Imports with the following code:
qmlRegisterType<TabViewHeaderBgQc>("MyControls", 1, 0, "TabViewHeaderBg");
Addition #2.
Qt 5.8, Windows 10, Visual Studio 2015.
My cpp application has a QMainWindow derived class, with QQuickView widget in the ui. Inside the view are a number of QML items which accept keyboard input. When an item is clicked, I call forceActiveFocus() on the clicked item. It all works from the time I launch the application, until the time I switch to another window.
If I switch to another application and back, or switch to another window within my application and back, calling forceActiveFocus() has no effect. The items are of a few different types, but here is a sample item:
TextInput {
id: textInput
anchors.fill: parent
inputMethodHints: Qt.ImhFormattedNumbersOnly
onActiveFocusChanged: console.log(activeFocus)
onEditingFinished:
{
}
MouseArea {
anchors.fill: textInput
onClicked: {
textInput.forceActiveFocus()
console.log("clicked")
}
}
}
When switching away I see activeFocus logged to the console as false. When I switch back again and click on the item, "clicked" is logged to the console, so the mouse event is being handled. However, onActiveFocusChanged is never called again.
I also tried an implementation with a FocusScope as the parent of the item, got the same behavior, with focus following my click until the point I switch away to some other window and back again.
UPDATE
After reading the comment from #user2436719, I've tried two minimal examples. It is only when using the QQuickView that this problem arises. Here is the QML app using a QML Window with the following main.qrc, which works just fine:
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
color: "lightblue"
Rectangle {
id: textInputRect
color: "white"
height: 50
width: 150
anchors.centerIn: parent
TextInput {
id: textInput
anchors.fill: parent
inputMethodHints: Qt.ImhFormattedNumbersOnly
onActiveFocusChanged: console.log(activeFocus)
onEditingFinished:
{
}
}
}
}
The second is a CPP application with a QMainWindow derived class that has a QQuickView widget in the ui. This version exhibits the problem behavior. Here is the main.qrc:
import QtQuick 2.7
import QtQuick.Window 2.2
Rectangle {
anchors.fill: parent
color: "lightblue"
Rectangle {
id: textInputRect
color: "white"
height: 50
width: 150
anchors.centerIn: parent
TextInput {
id: textInput
anchors.fill: parent
inputMethodHints: Qt.ImhFormattedNumbersOnly
onActiveFocusChanged: console.log(activeFocus)
onEditingFinished:
{
}
}
}
}
From the QQuickView version, here is main:
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
MainWindow window;
QQuickView* view = new QQuickView;
window.setView(view);
window.show();
return app.exec();
}
And here is how I set the QQuickView in the MainWindow:
void MainWindow::setView(QQuickView *value)
{
view = value;
QWidget *container = QWidget::createWindowContainer(view, this);
view->setSource(QUrl("qrc:/main.qml"));
ui->verticalLayout->addWidget(container);
}
Okay, this is QTBUG-34414. At the bottom of the comments for that bug a workaround is posted. The posted workaround fixed the problem for me, in the case of switching to other windows of my application, or to other applications. There was still no focus after closing dialogs. Putting this override in my window class fixed the problem for both cases:
bool MyWindow::event(QEvent *event)
{
if (event->type() == QEvent::ActivationChange ||
event->type() == QEvent::WindowUnblocked) {
if(view->isActive()) { //view is pointer to my QQuickView
window()->activateWindow();
return true;
}
}
// handle events that don't match
return QWidget::event(event);
}
This override worked for me with Qt 5.7 on OSX Sierra.
I've successfully exposed a C++ class to QML. It is registered and found in Qt Creator. It's purpose is to connect to a database, as shown in following code:
#ifndef UESQLDATABASE_H
#define UESQLDATABASE_H
#include <QObject>
#include <QtSql/QSqlDatabase>
class UeSqlDatabase : public QObject
{
Q_OBJECT
Q_PROPERTY(bool m_ueConnected READ isConnected WRITE setConnected NOTIFY ueConnectedChanged)
private:
bool m_ueConneted;
inline void setConnected(const bool& ueConnected)
{ this->m_ueConneted=ueConnected; }
public:
explicit UeSqlDatabase(QObject *parent = 0);
Q_INVOKABLE inline const bool& isConnected() const
{ return this->m_ueConneted; }
~UeSqlDatabase();
signals:
void ueConnectedChanged();
public slots:
void ueConnectToDatabase (const QString& ueStrHost, const QString& ueStrDatabase,
const QString& ueStrUsername, const QString& ueStrPassword);
};
#endif // UESQLDATABASE_H
However, when I try to call method isConnected() from the following QML code
import QtQuick 2.0
Rectangle
{
id: ueMenuButton
property string ueText;
width: 192
height: 64
radius: 8
states: [
State
{
name: "ueStateSelected"
PropertyChanges
{
target: gradientStop1
color: "#000000"
}
PropertyChanges
{
target: gradientStop2
color: "#3fe400"
}
}
]
gradient: Gradient
{
GradientStop
{
id: gradientStop1
position: 0
color: "#000000"
}
GradientStop
{
position: 0.741
color: "#363636"
}
GradientStop
{
id: gradientStop2
position: 1
color: "#868686"
}
}
border.color: "#ffffff"
border.width: 2
antialiasing: true
Text
{
id: ueButtonText
color: "#ffffff"
text: qsTr(ueText)
clip: false
z: 0
scale: 1
rotation: 0
font.strikeout: false
anchors.fill: parent
font.bold: true
style: Text.Outline
textFormat: Text.RichText
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 16
}
MouseArea
{
id: ueClickArea
antialiasing: true
anchors.fill: parent
onClicked:
{
uePosDatabase.ueConnectToDatabase("127.0.0.1",
"testDb",
"testUser",
"testPassword");
if(uePosDatabase.isConnected()==true)
{
ueMenuButton.state="ueStateSelected";
}
else
{
ueMenuButton.state="base state"
}
}
}
}
I get the following error:
qrc:/UeMenuButton.qml:92: TypeError: Property 'isConnected' of object UeSqlDatabase(0x1772060) is not a function
What am I doing wrong?
You have this error because you have declared the property isConnected in C++ but you're calling it from QML in the wrong way: uePosDatabase.isConnected is the correct way, not uePosDatabase.isConnected().
If you want to call the function isConnected() you should change its name to differate it from the property, like getIsConnected(). Given your property declaration, you neither need to call this function directly nor you need to make it callable from QML with the Q_INVOKABLE macro.
You must first create the object you want in QML code and then use the function:
your QML Code:
import QtQuick 2.0
import QSqlDatabase 1.0
Rectangle {
id: ueMenuButton
QSqlDatabase {
id: uePosDatabase
}
.
.
.
MouseArea
{
id: ueClickArea
antialiasing: true
anchors.fill: parent
onClicked: {
console.log(uePosDatabase.isConnected())
}
}
}
Be aware in case of an error some lines earlier in the same .js file can lead to this problem. The following code will not be executed.
I'm currently trying to implement a drag-to-create mechanism in QML, but I've hit upon a problem where I need the newly created MouseArea to become the target of mouse events even though the original MouseArea hasn't had a mouse button release event yet.
Window {
id: window
width: 300
height: 300
Rectangle {
id: base
width: 20
height: 20
color: "red"
MouseArea {
anchors.fill: parent
property var lastPoint
property var draggedObj: null
function vecLength( vec ) {
return Math.abs( Math.sqrt( Math.pow( vec.x, 2 ) +
Math.pow( vec.y, 2 ) ) );
}
onPressed: lastPoint = Qt.point( mouse.x, mouse.y )
onPositionChanged: {
if ( !draggedObj ) {
var diff = Qt.point( mouse.x - lastPoint.x,
mouse.y - lastPoint.y );
if ( vecLength( diff ) > 4 ) {
draggedObj = dragObj.createObject( window );
}
}
mouse.accepted = !draggedObj;
}
}
}
Component {
id: dragObj
Rectangle {
width: 20
height: 20
color: "blue"
Drag.active: dragArea.drag.active
Drag.hotSpot.x: 10
Drag.hotSpot.y: 10
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
}
}
}
If you run this code and try it, you will see that dragging in the red Rectangle causes the creation of the draggable blue Rectangle, but it won't follow the mouse because the red MouseArea is still receiving the mouse events despite the blue MouseArea being above it.
Is there any way of forcing the blue MouseArea to receive the mouse events?
I experienced with this before and had a beginning of solution in my attic.
The trick here is calling QQuickItem::grabMouse() and sending a mouse press event to the newly created object.
Unfortunately I believe this can only be done from c++.
I then created a helper class to expose this functionality to qml:
MouseGrabber.h
#ifndef MOUSEGRABBER
#define MOUSEGRABBER
#include <QObject>
#include <QQuickItem>
#include <QGuiApplication>
#include <QMouseEvent>
class MouseGrabber : public QObject
{
Q_OBJECT
Q_PROPERTY(QQuickItem* target READ target WRITE setTarget NOTIFY targetChanged)
Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
public:
explicit MouseGrabber(QObject *parent = 0) : QObject(parent), m_target(nullptr), m_active(true) { }
QQuickItem* target() const { return m_target; }
bool active() const { return m_active;}
signals:
void targetChanged();
void activeChanged();
public slots:
void setTarget(QQuickItem* target)
{
if (m_target == target)
return;
ungrabMouse(m_target);
if (m_active)
grabMouse(target);
m_target = target;
emit targetChanged();
}
void setActive(bool arg)
{
if (m_active == arg)
return;
m_active = arg;
if (m_active)
grabMouse(m_target);
else
ungrabMouse(m_target);
emit activeChanged();
}
private:
static void grabMouse(QQuickItem* target)
{
if (target)
{
target->grabMouse();
QMouseEvent event(QEvent::MouseButtonPress, QPointF(), Qt::LeftButton, QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers());
QGuiApplication::sendEvent(target, &event);
}
}
static void ungrabMouse(QQuickItem* target)
{
if (target)
target->ungrabMouse();
}
QQuickItem* m_target;
bool m_active;
};
#endif // MOUSEGRABBER
This could have been made more convenient by directly calling slots instead of manipulating proprieties, but that's what I had in stock. For example a slot called grabMouseUntilRelease(QQuickItem* item), that grabs the mouse for this item, listen for a mouse release event with installEventFilter and ungrab it automatically.
Register the class so it can be instantiated in QML with qmlRegisterType somewhere in your code :
qmlRegisterType<MouseGrabber>("com.mycompany.qmlcomponents", 1, 0, "MouseGrabber");
After that you can instantiate a MouseGrabber in QML and use it by modifying its proprieties ( target and active ) :
QML
import com.mycompany.qmlcomponents 1.0
Window {
id: window
width: 300
height: 300
Rectangle {
id: base
width: 20
height: 20
color: "red"
MouseArea {
anchors.fill: parent
property var lastPoint
property var draggedObj: null
function vecLength( vec ) {
return Math.abs( Math.sqrt( Math.pow( vec.x, 2 ) +
Math.pow( vec.y, 2 ) ) );
}
onPressed: lastPoint = Qt.point( mouse.x, mouse.y )
onPositionChanged: {
if ( !draggedObj ) {
var diff = Qt.point( mouse.x - lastPoint.x,
mouse.y - lastPoint.y );
if ( vecLength( diff ) > 4 ) {
draggedObj = dragObj.createObject( window );
grabber.target = draggedObj.dragArea; // grab the mouse
}
}
mouse.accepted = !draggedObj;
}
}
}
MouseGrabber {
id: grabber
}
Component {
id: dragObj
Rectangle {
property alias dragArea: dragArea
width: 20
height: 20
color: "blue"
Drag.active: dragArea.drag.active
Drag.hotSpot.x: 10
Drag.hotSpot.y: 10
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
onReleased: {
if (grabber.target === this)
grabber.target = null; // ungrab the mouse
}
}
}
}
}
My other answer is way too much over engineered.
There is no need to steal the mouse events in your situation, you just want to update the position of the dragged blue rectangle in the onPositionChanged handler (or with a Binding or directly inside the Rectangle component).
Wrtiting this in your MouseArea is enough :
onPositionChanged: {
if ( !draggedObj ) {
var diff = Qt.point( mouse.x - lastPoint.x,
mouse.y - lastPoint.y );
if ( vecLength( diff ) > 4 ) {
draggedObj = dragObj.createObject( window );
}
} else { //update the position of the dragged rectangle
draggedObj.x = mouse.x - draggedObj.width/2;
draggedObj.y = mouse.y - draggedObj.height/2;
}
}
onReleased: draggedObj = null