I'm having trouble binding an "On Close" event for my application written in QtQuick. What I'd like to do is do the standard "confirm exit" method and maybe I'm going about this the wrong way.
As I understand it I want something like
void MainDriver::onClose(QEvent* event)
{
if(notSaved)
{
//prompt save
event->ignore();
}
else
event->accept();
}
however it seems QQuickCloseEvent isn't derived from QEvent or I'm including the wrong header (very possible) and I can't find out where it is defined so that I can connect the signals.
Is there a better way to get around this? Right now I instantiate the main window like this:
QQmlApplicationEngine engine; //Actually initialized in the constructor
engine.load(QUrl("qrc:/qml/Window.qml"));
QObject *topLevel = engine.rootObjects().value(0);
QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
I'm using ApplicationWindow (QtQuick Controls) as the main window which is derived from QWindow. I'm open to suggestion here, I'd like to stick to QtQuick and not wrap everything in a standard QWindow or QMainWindow, but maybe that's a poor route to take. Any help would be appreciated.
You can use EventFilter to handle close event in main window's controller:
class MyEventFilter : public QObject
{
Q_OBJECT
protected:
bool eventFilter(QObject *obj, QEvent *event);
};
bool MyEventFilter::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::Close)
{
// TODO: confirm
return true;
} else
{
// standard event processing
return QObject::eventFilter(obj, event);
}
}
And in your main():
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
MyEventFilter filter;
QtQuick2ApplicationViewer viewer;
viewer.installEventFilter(&filter);
viewer.setMainQmlFile(QStringLiteral("qml/QCloseConfirm/main.qml"));
viewer.showExpanded();
return app.exec();
}
Here is example. But it doesn't seem to be perfect. There should be a better solution.
Related
I have a ComboBox and set it to be edited.
QComboBox *myCombo = new QComboBox(this);
myCombo->setEditable(true);
myCombo->setStyleSheet("QComboBox::down-arrow{image: url(:/bulb.png);}");
myCombo->setCursor( QCursor( Qt::PointingHandCursor ) );
So now when i click onto the editing field, nothing happen. But what I need is, when I click onto the bulb (which is the down-arrow), something (like a table or a dialog....) should be appeared. How can I recognize this click event in this case? I looked at the list of signals for combo box but could not find any signal for that.
By overwriting the mousePressEvent() method you must use hitTestComplexControl() method to know that QStyle::SubControl has been pressed by issuing a signal if it is QStyle::SC_ComboBoxArrow.
#include <QtWidgets>
class ComboBox: public QComboBox
{
Q_OBJECT
public:
using QComboBox::QComboBox;
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent *event) override{
QComboBox::mousePressEvent(event);
QStyleOptionComboBox opt;
initStyleOption(&opt);
QStyle::SubControl sc = style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, event->pos(), this);
if(sc == QStyle::SC_ComboBoxArrow)
emit clicked();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ComboBox w;
w.setEditable(true);
w.setStyleSheet("QComboBox::down-arrow{image: url(:/bulb.png);}");
QObject::connect(&w, &ComboBox::clicked, [](){
qDebug()<<"clicked";
});
w.show();
return a.exec();
}
#include "main.moc"
Although showPopup() is a possible option this can be called directly without the down-arrow being pressed, for example by calling it directly: myCombo->showPopup() so it is not the correct option.
A possible solution is to subclass QComboBox and reimplement showPopup() virtual method:
.h:
#ifndef COMBOBOXDROPDOWN_H
#define COMBOBOXDROPDOWN_H
#include <QComboBox>
#include <QDebug>
class ComboBoxDropDown : public QComboBox
{
public:
ComboBoxDropDown(QWidget *parent = nullptr);
void showPopup() override;
};
#endif // COMBOBOXDROPDOWN_H
.cpp:
#include "comboboxdropdown.h"
ComboBoxDropDown::ComboBoxDropDown(QWidget *parent)
: QComboBox (parent)
{
}
void ComboBoxDropDown::showPopup()
{
//QComboBox::showPopup();
qDebug() << "Do something";
}
I'm brand new to c++ and QML, and am struggling to call and run a threaded loop. I'd like to add an integer to a QML propery value every x milliseconds.
I'll omit my main.qml code for now, as I'm able to access the desired property; this question pertains more to c++.
void fn(QQuickItem x)
{
for (;;)
{
std::this_thread::sleep_for(std::chrono.milliseconds(100));
x->setProperty("value", x->property("value").toReal() + 10);
}
}
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
//QScopedPointer<TachometerDemo> Tacho(new TachometerDemo);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
QQuickItem *item = engine.rootObjects().at(0)->findChild<QQuickItem*>
("circularGauge");
thread t1(fn, item);
return app.exec();
}
Any guidance on the most efficient means for achieving the above desired functionality would be appreciated. Whilst I'm currently testing on a win32 platform, a cross-platform approach is required.
Better use Qt's event loop mechanism and SIGNAL/SLOTS. And QThreads if needed. Also if you need to update value in QML in msecconds duration don't do it over setProperty, this function call takes too long. You can update your value directly in QML using JS. But if you need to update QML value from C++ you can do it this way:
test.h
#include <QObject>
#include <QTimer>
class Test : public QObject
{
Q_OBJECT
public:
Test();
Q_PROPERTY(int value READ value NOTIFY valueChanged)
int value(){return this->m_value;}
signals:
void valueChanged();
private slots:
void timeout();
private:
int m_value;
QTimer * m_timer;
};
test.cpp
#include "test.h"
Test::Test():m_value(0)
{
this->m_timer = new QTimer(this);
this->m_timer->setInterval(100);
connect(this->m_timer, &QTimer::timeout, this, &Test::timeout);
this->m_timer->start();
}
void Test::timeout()
{
this->m_value += 10;
emit valueChanged();
}
in your main.cpp
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("Test"), new Test());
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
Somewhere in your QML:
Label
{
text: Test.value
anchors.centerIn: parent
}
This is the fastest way to update QML value from C++
I want to write mechanics in c++ and objects, signals from qml, how can I connect my main.qml with main.cpp ?
The best option will be (if it is possible) declaration, adding something like directory which make qml and c++ as one database without everytime slot and signal reference between two files
From the documentation, here's one way to connect QML objects to C++:
// MyItem.qml
import QtQuick 2.0
Item {
id: item
width: 100; height: 100
signal qmlSignal(string msg)
MouseArea {
anchors.fill: parent
onClicked: item.qmlSignal("Hello from QML")
}
}
class MyClass : public QObject
{
Q_OBJECT
public slots:
void cppSlot(const QString &msg) {
qDebug() << "Called the C++ slot with message:" << msg;
}
};
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQuickView view(QUrl::fromLocalFile("MyItem.qml"));
QObject *item = view.rootObject();
MyClass myClass;
QObject::connect(item, SIGNAL(qmlSignal(QString)),
&myClass, SLOT(cppSlot(QString)));
view.show();
return app.exec();
}
And sorry about the vague title, i don't know how to express it better, so pardon me.
I got a class to store data, inherit from QObject, and a loginSignal
class UserData : public QObject {
Q_OBJECT
public:
Q_INVOKABLE QString login(const QString p_user, const QString p_password, const bool p_remember);
UserData(QObject* parent = 0);
signals:
void userLogin();
};
Register class to QML
qmlRegisterType<UserData>("UserData",1,0,"UserData");
Connect to a QML Element
ApplicationWindow {
UserData {
id:userData
}
Rectangle {
Connections {
target: userData
onUserLogin : {
doSomething()
}
}
}
}
However if i call emit(userLogin()) signal inside login() function, nothing happend in QML
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine(QUrl("qrc:///mainControl.qml"));
QQmlContext* context = engine.rootContext();
UserData userData;
context->setContextProperty("userData",&userData);
userData.login(user, pass, true);
return app.exec();
}
Maybe QML Component hasn't complete finished yet?
And thank for dropping by
Edit : Look like i accident create a second instance in QML, and use it. Everything working fine now :D
I have a QMainWindow which contains a circular QWidget inside it. In order to have a circular shaped QWidget I am making use of QWidget::setMask. The intended behaviour of the application on mouse press is inside the MainWindow depends on the region on which mouse is pressed.
class MyMainWindow: public QMainWindow
{
public:
MyMainWindow():QMainWindow()
{
}
void mousePressEvent( QMouseEvent * event )
{
qDebug()<<"Clicked on MainWindow";
}
};
class CircularWidget: public QWidget
{
public:
CircularWidget( QWidget * parent ):QWidget( parent )
{
}
void paintEvent(QPaintEvent * event)
{
QRegion circularMask( QRect( pos(), size() ), QRegion::Ellipse );
setMask( circularMask );
setStyleSheet("background-color:black;");
QWidget::paintEvent( event );
}
void mousePressEvent( QMouseEvent * event )
{
qDebug()<<"Clicked on Circular Widget";
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow w;
w.resize( 400, 400 );
CircularWidget circularWidget( &w );
circularWidget.show();
w.setCentralWidget( &circularWidget );
w.show();
return a.exec();
}
Currently, when I click inside the circular region I get the event in my Widget. But all the mouse press events outside the circle are lost. I saw in the Qt Documentations that: Masked widgets receive mouse events only on their visible portions. Is there any way to transfer the other mouse click events (events on the grey region in the picture) to the parent widget?
The reason why you did not get mouse events in your MyMainWindow class is because in your main() function you create the main window object of class QMainWindow instead.
For handling the mouse click events on the circular widget I would use signal/slot mechanism here. I.e. let the circular widget emit a signal each time it is clicked. Any other receiver will handle it as its own. For example:
class MyMainWindow: public QMainWindow
{
Q_OBJECT
[..]
public slots:
void handleMouseClick(const QPoint &) {}
};
class CircularWidget: public QWidget
{
Q_OBJECT
public:
[..]
void mousePressEvent( QMouseEvent * event )
{ emit mouseClicked(event->pos()); }
signals:
void mouseClicked(const QPoint &);
};
And finally establish connection. With this in MyMainWindow class you will be able to handle mouse click events both from circular widget and main window itself.
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyMainWindow w; // <-------- This should be MyMainWindow instead of QMainWindow
CircularWidget circularWidget( &w );
w.setCentralWidget( &circularWidget );
QObject::connect(&circularWidget, SIGNAL(mouseClicked(const QPoint &)),
&w, SLOT(handleMouseClick(const QPoint &));
return a.exec();
}
Proper solution is reject mouse press event if pointer is outside of region so more or less it should look like this:
void mousePressEvent( QMouseEvent * event )
{
QRegion circularMask( QRect( pos(), size() ), QRegion::Ellipse ); // it would be nice to make it a class field
if (!circularMask.contains(event->pos()) {
event->ignore();
}
}
See documentation:
A mouse event contains a special accept flag that indicates whether
the receiver wants the event. You should call ignore() if the mouse
event is not handled by your widget. A mouse event is propagated up
the parent widget chain until a widget accepts it with accept(), or an
event filter consumes it.