Tumbler in C++/Qt - c++

I'm trying to implement a tumbler to set the time for an alarm in C++. Yet I've only seen tumblers in Qt quick and therefore coded in QML. Now I've tried to get QML code in my C++ code by doing:
void SmartAlarm::showTumbler(){
// Create the QML view
QQuickView* quickView = new QQuickView(QUrl(":/files/includes/AlarmTumbler.qml"));
// Make the QML view resize when the parent is resized
quickView->setResizeMode(QQuickView::SizeRootObjectToView);
QWidget* quickWidget = QWidget::createWindowContainer(quickView);
rightLayout->addWidget(quickWidget);
}
My QML file looks like this:
import QtQuick 2.12
import QtQuick.Window 2.2
import QtQuick.Controls 2.12
import QtQuick.Extras 1.4
TumblerColumn{
id: weekdayTumbler
model: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
}
TumblerColumn {
id: hourTumbler
model: 24
}
TumblerColumn {
id: minuteTumbler
model: 60
}
All I get is a blank, white widget in my layout. What am I doing wrong? Is there a way to implement a tumbler in Qt without using QMLs?

I think it doesn't find the tumbler-file. You can check this the easiest by starting the program and check in the Application Output (Bottom Menu in QTCreator) for following message:
":/files/includes/AlarmTumbler.qml: No such file or directory"
If you can't find it, it might be because you use a Shadow Build and the actual execution-files are in a different folder than the QML Files. To solve this, you can go to "Projects" and deactivate "Shadow Build", rebuild and you should see the tumbler.
The implementation itself should work fine. I tested it locally, added everything to the MainWindow though cause I don't know where your "rightLayout" comes from.
ui->setupUi(this);
// Create the QML view
QQuickView* quickView = new QQuickView(QUrl("tumbler.qml"));
// Make the QML view resize when the parent is resized
quickView->setResizeMode(QQuickView::SizeRootObjectToView);
QWidget* quickWidget = QWidget::createWindowContainer(quickView);
this->ui->rightLayout->addWidget(quickWidget);

Related

Add a QTreeView in QML

I would like to register a QTreeView c++ object to QML.
I tried to register it like this:
main.cpp:
qmlRegisterType<QTreeView>("com.MyApp.QTreeView", 1, 0, "QTreeView");
relevant code in main.qml
import com.MyApp.QTreeView 1.0
QWindow {
QTreeView{
headerHidden: true
}
}
Result: it compiles. headerHidden property is found so it is registered correctly. However I have an error at runtime:
ASSERT: "!d->isWidget" in file kernel\qobject.cpp, line 2090
QWidgets are not directly compatible with QML such that they can be embedded in a QML view. They are two different UI technologies and cannot be used together in that fashion.
You can however embed a QML view inside of a QWidget hierarchy:
https://www.ics.com/blog/combining-qt-widgets-and-qml-qwidgetcreatewindowcontainer
Or just use the QML TreeView component instead:
https://doc.qt.io/qttreeview/qml-treeview.html

Signal-Slot connection after destruction of qml objects?

What happens with the connection of signals from c++ objects to qml objects slots after destruction of qml objects?
Item {
function qmlFunction() {
cppObject.cppObjectFunction()
}
Component.onCompleted: {
cppObject.someSignal.connect(qmlFunction);
}
Component.onDestruction: {
cppObject.someSignal.disconnect(qmlFunction);
}
}
Before I wrote Component.onDestruction with disconnect() the program displayed an error message:
qrc:/qml/xxxxxxxx.qml:77: TypeError: Cannot call method 'cppObjectFunction' of undefined
Is the disconnection of signals and slots not performed automatically?
Object cppObject "always" exist and passed to qml this way:
main.cpp
QQmlApplicationEngine engine;
Model* model = new Model::instance(&engine);
engine.setObjectOwnership(model, QQmlEngine::CppOwnership);
engine.rootContext()->setContextProperty("cppObject", model);
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
QML item is loaded by the Loader and can be reloaded several times during the program. Naturally, the error occurs after the QML item has been reloaded and cppObject triggers a signal someSignal.
on windows: Qt 5.6.2 <- my program log error to debug console
on linux: Qt 5.9.2 <- my program crashes
main.qml
import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.2
import QtQuick.Controls.Styles 1.3
import QtQuick.Window 2.2
File with error
import QtQuick 2.3
import QtQuick.Layouts 1.1
import QtQuick.Controls 1.3
Easy solution is to :
istantiate the cppObject directly from QML to ensure that both objects are properly cleaned up by adding to your main.cpp file before you load the QML file
qmlRegisterType<Model>("com.your.app", 1, 0, "CppModel");
and then in your QML file include:
import com.your.app 1.0
....
CppModel {
id: cppObject
onSomeSignal: { cppObject.cppObjectFunction(); }
}
This way will make the extra connections and the extra qmlFunction() both unnecessary.
and maybe this too:
If cppObjectFunction() is a public slot from cppObject that is defined on the C++ side, you can try adding Q_INVOKABLE to the front of it in order to add it to the metaobject system.
Q_INVOKABLE void cppObjectFunction() { /* your code */ }

Force signal/slot hookup of not-yet-visible QML item on startup

In Qt's QML language for writing GUI code, QML elements are (if I understand correctly) not actually created until they become visible. (EDIT: It sounds like the elements are created when the QML engine loads them, but it appears that signal/slot connections are not made unless the elements are visible.)
I have some QML elements (LineSeries objects from QtCharts) that record some data over time, and I would like them to start recording data as soon as my app starts up, even though the ChartView elements containing each series aren't immediately visible (users must navigate to a page containing these elements).
Is this possible?
One approach might be to keep each data series itself in some kind of QVariantList containing QPointFs in a global QML object, then dynamically assign it to the desired LineSeries object when the parent ChartView is instantiated. This might be possible using ChartView::createSeries, though I believe the only way to populate the new series would be to call ChartView::series() and pass that to some kind of Q_INVOKABLE method in my C++ backend that would populate the series.
EDIT: Not sure if this is relevant, but the GUI element I'm using for navigation (i.e. the reason the ChartView isn't visible on startup) is a TabView. The ChartView objects are not top-level TabView pages; they're a couple levels down.
EDIT 2: The answer below seems like it should work, but I get the rather unhelpful error TypeError: Type error when I try to implement it. I've put together a minimal (non-)working example here (use qmlscene to run it; I am using Qt 5.5).
EDIT 3: The above non-working version was fixed by simply adding a property alias, thanks to Mitch's answer and comments.
Items in Qt Quick are created as soon as the QML they reside in is loaded by the QML engine (even if they default to non-visible). The exception to this rule are items that dynamically load their content, like Loader.
In your edit, you said that you're using TabView. I think that the setup you have is something like this:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Hello World")
width: 800
height: 700
visible: true
TabView {
id: sv
anchors.fill: parent
Tab {
title: "Page 1"
}
Tab {
title: "Page 2"
ChartView {}
}
}
}
Try changing it to something like this:
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Hello World")
width: 800
height: 700
visible: true
ChartView {
id: chartView
color: "red"
anchors.fill: parent
visible: false
Component.onCompleted: print("Monitoring data...")
}
TabView {
id: sv
anchors.fill: parent
Tab {
id: tab1
title: "Page 1"
}
Tab {
id: tab2
title: "Page 2"
onActiveChanged: {
if (active) {
chartView.parent = tab2;
chartView.visible = true;
}
}
}
}
}
Tabs are basically just Loaders, so they have all of Loader's API, including the active property. This property lets you know when that tab has been loaded. Before that stage, it's inaccessible.
When the tab becomes active, we reparent the ChartView to it so that it's displayed in the correct place, and then show it.
As you can see from the debug output, the ChartView is created at startup. You can move it somewhere else to control when it's loaded, or... use a Loader. :)
EDIT: Based on this answer and the discussion in the comments, OP has created a small working example that demonstrates how persistent items can be pre-loaded and re-parented.

Extending QML ApplicationWindow in C++

How can you properly extend the QML ApplicationWindow type? According to the documentation, ApplicationWindow instantiates a QQuickWindow. So I tried sub classing from QQuickWindow and exposing the type to QML as MyWindow. The problem is that MyWindow doesn't actually extend the QML type ApplicationWindow, so you don't get all the properties like menuBar and toolBar. How can I extend ApplicationWindow in C++ and expose it to QML? Here is what I'm currently doing:
class MyQuickWindow : public QQuickWindow
{
//...irrelevant additions
}
int main()
{
QGuiApplication app(argc, argv);
qmlRegisterType<MyQuickWindow>("MyExtensions", 1, 0, "MyApplicationWindow");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
}
Here is the QML file:
import QtQuick 2.0
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1
import MyExtensions 1.0
MyApplicationWindow {
width: 800
height: 600
visible: true
menuBar: MenuBar { // ERROR: menuBar is not a property
Menu {
title: "File"
MenuItem { text: "New" }
MenuItem { text: "Open" }
}
}
}
Note that I need to have my own additions in C++ to the QQuickWindow for other reasons.
It seems like the job for qmlRegisterType. And it is a bit hard to say if you miss something with your C++ part but registering the type should help. That is for exposing the type itself and should enable the derived QQuickWindow functionality (derived from ApplicationWindow actually). But for what you are adding you need to deal with Q_PROPERTY and Q_INVOKABLE (which is for functions) mechanism. See the whole bunch of Q_* QObject macro.
And if that was not enough then there is an example for such inheritance.
Correction: the author is dealing with QML-made type but he can still try to mimic the type on his own. The path to ApplicationWindow.qml source code is: C:\Qt\5.3\Src\qtquickcontrols\src\controls where C:\Qt\5.3\ is the root for selected Qt version.I would attempt that and that is feasible unless we want to find out about explicit QML inheritance. That file can also be found at Qt source code repository.

Include another QML file from a QML file

There's another question on Stackoverflow about this matter but I don't find the accepted solution possible. So I ask again because the old question is out of attention.
The situation is this way. I have application screens defined by 'main.qml', 'feature1.qml', 'feature2.qml'.
These screens share the same toolbar below title bar. The toolbar has multiple items so copy-paste the QML code is like crazy. This question: QML file include - or one monolithic file (structure QML code)? says it's possible to just use QML file name as component name but I can't get it working.
Any solution? with details pls.
Let's assume you have a file called main.qml and a component in another file called MyCustomText.qml. If both files are in the same directory you can directly load the component like this:
// in Main.qml
Rectangle {
id: root
MyCustomText {
text: "This is my custom text element"
}
}
If MyCustomText.qml is in another subdirectory MyComponents for example to group all your custom components together, you first need to import the directory before using the component the same way:
// in Main.qml
import "MyComponents"
Rectangle {
id: root
MyCustomText {
text: "This is my custom text element"
}
}
Another important thing to note is that your QML files should always start with an uppercase letter if you want to be able to use them this way
Of course your Loader solution works too but this is the easiest way to import QML files in other components.
Finally I have dug it out from internet. Let's say the to-be-included file is 'mycomponent.qml' in this directory structure (Qt Quick):
projectdir/
qml/
projectname/
main.qml
mycomponent.qml
The content of 'mycomponent.qml' (for example):
Text {
text:"Hello, Scooby Doo!";
}
We have to load it this way (in 'main.qml'):
Rectangle {
...
Loader {
source:"mycomponent.qml";
}
...
}
See Qt documentation about reuseable components.
The imported QML file defines a type whose name is the same as the filename (capitalized, less the .qml suffix). QML calls the type a reuseable component. You use that type name to instantiate an object in the importing QML document (file.)
Its not like a C language include, where the text of the included file is inserted into the including file. Its more like importing the name of a class in Python, and then instantiating an object of that class in the importing file. Or somewhat similar to Javascript, the imported file is creating a prototype object, and the importing file is prototypically inheriting from it. Except note the discussion about the root object and what properties of the component will be visible (because of QML's document scoping.) You won't be able to access everything in the imported file as if it were a C include, a Python import, or a JS inheritance.
It's easy like that. Put all your file components in a folder like "components". In your case, the name of the file can be Toolbar.qml. Write the QML code for you toolbar, my example will draw a red rectangle.
import QtQuick 2.6
Item {
width: 500
height: 100
Rectangle {
width: 500
height: 100
color: "red"
radius: width * 0.5
}
}
And then, in your screens which you want to use this component (for example, file main.qml), is simple like that:
import "components" as Components
Components.Toolbar {
Layout.fillHeight: true
}
Take care about the location of files, and still all components should start with a Caps letter, in this example:
\main.qml
\components\Toolbar.qml
You can just call the Name of the qml.
for ex.
I have 2 qml file.
The main.qml and Merchant.qml
I just called the Merchant. it should be showed in intellisense.
ApplicationWindow {
id: mainWindow
visible: true
Component{
id: merchantsComponent
Merchant{
id: merchants
width: mainWindow.width
height: mainWindow.height
}
}
}
You can just call that compenent to Loader
You can directly call:
Window {
id: mainWindow
visible: true
Feature1{}
}
like this, to load Feature1.qml