I have a gauge.qml as shown below
Gauge.qml
Item {
property real gaugevalue //this determine the gaugevalue.
width: 100
height: 100
Rectangle {
Image {
x: 0
y: 0
source: "qrc:/gaugebg.png"
}
Image {
x: 50
y: 49
source: "qrc:/needle.png";
rotation: 0
// I am doing some rotation logic with gaugevalue. please ignore logic for now
transform: Rotation {
origin.x: 10; origin.y:10;
angle: gaugevalue
}
}
}
}
Also, I have a main.qml as shown below, in which I have placed my gauge
main.qml
Window {
id: main
width: 640
height: 480
//gauge position
property int gaugeX: 50
property int gaugeY: 60
Gauge {
id : id_gauge
gaugevalue : 50 //Now I am setting the gaugevalue from here. I need this to be controlled from my GaugeController class.
x:gaugeX
y:gaugeY
}
}
What I am trying to achieve is make my code fit into MVC architecture. I am planning to write a 'GaugeController' class in C++ which will set the 'gaugevalue' (and more properties later). I really confused by reading many articles which leads to think in different way (I see many solution like using Q_PROPERTY or setProperty etc. (refer to this link : https://www.zhieng.com/exposing-qml-object-pointer-to-cc/). So It would be great if you could correct my code to achieve this in MVC architecture.
using qmlRegisterType<>() and then create an instance of GuageController in your QML file which can be used to control Guage by passing values via signals/slots
main.cpp
qmlRegisterType<GaugeController>("myApp", 1, 0, "GaugeController");
main.qml
import myApp 1.0
...
GaugeController {
id: id_controller
// heres how you implment a method as a SLOT via QML
onGaugeValueChanged(gaugeValue): {
id_gauge.gaugeValue = id_controller.gaugeValue
}
}
gaugeController.cpp
class GaugeController : public QQuickItem
{
Q_OBJECT
// Q_PROPERTY MACRO allows the property to be read by QML
Q_PROPERTY(int gaugeValue READ gaugeValue WRITE setGaugeValue NOTIFY gaugeValueChanged)
public:
int gaugeValue() { return _gaugeValue; };
signals:
void gaugeValueChanged();
public slots:
void setGaugeValue(newVal) { _gaugeValue = newVal; }
private:
int _gaugeValue;
** EDIT **
from main.qml to pass parameters to GuageController
GaugeController {
id: controller
gaugeValue: 10
}
// or
function myfunction { controller.gaugeValue = 5 }
Related
I have 3 nested ListViews.The first is the month, second is day and third is hour of day.All of them constructs a calendar.There is a loader that loads a window when I click in the hour ListView, and set text in a Label.This text can be displayed in the hour view and can be deleted or edited through the above window .Unfortunately the text can not be saved because when I scroll the ListViews, delegates changing and not keeping the context of the label(That is normal I suppose).The objective is to be able to save those texts in the label(store the data) and restore them when the application is closed and re-opened.
Below is a generic code sample for 3 ListViews:
ApplicationWindow{
id:appwindow
............
Item{
id:dayView
...........
ListView{
id:monthofdayCalendar
orientation:Qt.Horizontal
model:12
delegate: Item{
ListView{
id:dayCalendar
orientation: Qt.Horizontal
model:32
delegate: Item{
...............
ListView{
id:daylistView
orientation: Qt.Vertical
model:24
delegate:Item{
id:hourItem
property string hourTime:hourweeklistviewLabel
property string notetaking:notesLabe
.............
MouseArea{
anchors.fill:parent
onClicked:{
windowLoader.active =true
daylistView.currentIndex=index
}
}
Rectangle{}
Label{
id:hourweeklistviewLabel
}
Label{
id:notesLabel
anchors.left:hourweeklistviewLabel.right
anchors.leftMargin: 30
text:""
}//Label
}//delegate:Item
}//ListView
} //delegate:Item
}//ListView
}//delegate:Item
}//Listview
}//Item
Below is the code of loader:
Loader {
id:windowLoader
focus: true
active:false
sourceComponent: Window{
id:inputWin
title:"Enter Note"
width:500
height:300
visible:true
onClosing:{
windowLoader.active=false
monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentIndex = calendarMonth.selectedDate.getDate() === new Date().getDate()
&& calendarMonth.selectedDate.getDay() === new Date().getDay()
&& calendarMonth.selectedDate.getMonth() === new Date().getMonth()?getHour():12
}
TextField {
id:title
x:50
y:20
placeholderText :'Enter Note'
text:monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentItem.notetaking.text
}
TextField{
id:timeDate
anchors.horizontalCenter: title.horizontalCenter
anchors.top:title.bottom
anchors.topMargin:10
placeholderText : calendarMonth.selectedDate.getDate() +"-"
+ (calendarMonth.selectedDate.getMonth()+1)+"-"
+ calendarMonth.selectedDate.getFullYear() + " "
+ monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentItem.hourTime.text
}
Button {
id: button
text: qsTr("Add Note")
anchors.centerIn:parent
onClicked: {
if (title.text !==""){monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentItem.notetaking.text= title.text}
else{}
}
}
}
}
The big question is how to save (store) the data of notesLabel.text and be able to display it and restore it every time I close and re-open the application.
As you can see the model for each ListView is not a ListModel so I think I can not use those models to save the data if I am right.If I am wrong please advise.
Anyway your help will be appreciateed.
EDIT
I've changed the integer models with ListModel dynamically created.The code of the ListModels is below:
ListModel{
id:hourlistModel
Component.onCompleted:{
for (var i = 0; i <25; i++){
append(createListElement())
}
}
property int h:0
function createListElement(){
return {
hour : h++
}
}
}
ListModel{
id:daylistModel
Component.onCompleted:{
for (var j=0; j <= 31; j++){
append(createListElement())
}
}
property int dD:0
function createListElement(){
return {
day : dD++
}
}
}
ListModel{
id:monthlistModel
Component.onCompleted:{
for (var k=0; k <=11; k++){
append(createListElement())
}
}
property int mN:0
function createListElement(){
return {
monthName : mN++
}
}
}
Can I store the data from Label notesLabel, now I've changed the models of ListViews with ListModels?
Thanks in advance.
I would create an exposed C++ class.
Using the exposed C++ class you have a range of options to pass data from the front/backend
Q_Property/Member
Q_Invokable
Signal/Slots
Given that you are using strictly strings, I would use an exposed series of QStrings or a QStringList.
QString
QStringList
To tackle to file read/write use your now exposed C++ class. You can either stick to file I/O via standard c++ or QFile system.
Constructor - Read the .txt file and save the data to your property data.
Exchange data as needed updating either the QML or C++ property member
Deconstructor - save the property member data back to file.
Brief example code:
someQML.qml
Import MyExposedClass 1.0
Item {
MyExposedClass {
id: myExposedClassID
text: myExposedClassID
}
Text{
id: yearTextID
text: myExposedClassID.year
}
Text{
id: monthTextID
text: myExposedClassID.month
}
Text{
id: dayTextID
text: myExposedClassID.day
}
Button {
id: myButtonID
onButtonPressed {
var finalStr = yearTextID + monthTextID + dayTextID
// If you used a QMember of qstring lets say
myExposedClassID.saveFile = finalStr
// If you used a QInvokable
myExposedClassID.saveFile_INVOK(finalStr)
}
}
}
myClass.h
class myClass : public QObject {
Q_OBJECT
// Serial Dev
Q_PROPERTY(QString day READ getDay WRITE setDay NOTIFY dayChanged)
Q_PROPERTY(QString month READ ... WRITE ... NOTIFY ...)
Q_PROPERTY(QString year READ ... WRITE ... NOTIFY ...)
...
// On construction, read the text file and update the qproperty variables
//implement getters and setters for qproperties
// On deconstruction, write the properties to file
}
If you have issues with MVC and QStrings/QStringlist. You may have to look into QVariants or QAbstracts.
I find QML to be a risky hole to dive into. Its easy to add more and more functionality to a QML file. But if you try and redesign it or change up some logic, it can very quickly ruin the QML. Splitting the QML into a QML & C++ is a nice way to achieve modularity and control.
I have one more question in connection with my QVariantMap. Currently I trigger the filling directly at startup (c++):
StyleConfiguration::StyleConfiguration(QObject *parent) : QObject(parent)
{
restore("q_button");
}
However, I would like to have the map filled directly at runtime in qlm, but don't know how to manage that.
Somethings like this:
xxxx: _styleconfiguration.restore("q_button")
Similar to a repeater:
Repeater{
model: _styleconfiguration.restore("q_button")
}
what should I use for xxx? Would be happy if someone can help me further.
addition:
mainWin.qml:
Rectangle {
id: dashboard
...
Row {
...
//----------------Button-------------//
Grid {
....
Component.onCompleted: _styleconfiguration.readValue("q_button") // **<- Call to read out the json**
Repeater {
id: dashboardButtons
model: _configuration.buttonMapping
Q_Button{ // **<- use read out values from the json**
id: dashButton
text: dashboard.functionsDashboard[modelData].text
...
}
}
}
}
//----------------Longpress Menu-------------//
Popup {
...
contentItem: Rectangle {
...
Column {
....
Component.onCompleted: _styleconfiguration.readValue("l_button")
Repeater {
model: dashboard.functionsDashboard
L_Button {
text: modelData.text.replace('\n', ' ')
....
}
}
}
}
}
}
}
Q_Button.qml:
Rectangle {
id: button
width: _styleconfiguration.styleMapping["width"]
height: _styleconfiguration.styleMapping["height"]
...
}
I hope the addition helps for understanding
I'm not sure I understand your question completely...
You have to mark your method declaration as Q_INVOKABLE in order to call it from QML:
Q_INVOKABLE void restore(const QString& s);
Then you can call it at creation of your QML Component.
Component.onCompleted: _styleconfiguration.restore("q_button")
I have a QStandardItemModel which I display using a TableView in qml. It uses QVariant to store the data. I want to have custom delegates depending on which type the stored data has, e.g. like this
Component {
id: myDelegate
Loader {
property var roleTwo: model.two
sourceComponent: if( CODE_FOR_MY_ITEM_HAS_BOOL_TYPE) {
checkBoxDelegate}
else { stringDelegate}
}
}
However, I don't know how to check the type of an item in my model. How can this be achieved?
Upon request, I provide more context to this question:
As posted here Custom Model for TableView or QStandardItemModel I want to have a TableView with two columns, a parameter name and a parameter value. The goal is to have a list of editable parameters which control the behaviour of an algorithm. For this, I use a QStandardItemModel defined like this:
class mystandardmodel: public QStandardItemModel
{
public:
mystandardmodel();
enum Role {
role1=Qt::UserRole,
role2
};
explicit mystandardmodel(QObject * parent = 0): QStandardItemModel(parent){}
//explicit mystandardmodel( int rows, int columns, QObject * parent = 0 )
// : QStandardItemModel(rows, columns, parent){}
QHash<int, QByteArray> roleNames() const{
QHash<int, QByteArray> roles;
roles[role1] = "one";
roles[role2] = "two";
return roles;
}
};
This model right now is displayed like this:
TableView {
id: tableView2
x: 69
y: 316
width: 318
height: 150
TableViewColumn {
title: "Parameter Name"
role: "one"
}
TableViewColumn {
title: "Value"
role: "two"
delegate: myDelegate
}
model: myTestModel
}
Component {
id: myDelegate
Loader {
property var roleTwo: model.two
sourceComponent: if(typeof(roleTwo)=='boolean') {
checkBoxDelegate}
else { stringDelegate}
}
}
Component {
id: checkBoxDelegate
CheckBox{text: roleTwo}
}
Component {
id: stringDelegate
Text {text: roleTwo
}
}
So is this the way you would do it? Also, I am happy for hints on who to make the model editable.
If typeof() works for your use-case, then you can roll with it.
What I personally like to do is have an integer member to denote the particular type. This has an array of advantages, here are some:
you can use the same underlying data type to drive different gui types, for example, a string might be a name or an info, one might be editable, the other read only
it works in cases where you might have fundamentally different model items with fundamentally different sets of data
you can use it as an index, and have an array of Components, and in the loader simply use sourceComponent: guitypes[type]
I am making a simple playlist player on qml. I mean there is a Audio player which plays files with extension .mp3 in a folder. But this "playlist player" assumes the whole folder as a playlist. So I give the path of the playlist folder as a command line argument to the program, for ex. ./playlist_player /home/user/playlist-folder and program plays whole mp3s in the playlist-folder folder. But since qml does not understands wildchars like asterisk(*), I use QDir to find the names of the mp3s to find the names of the mp3s and expose those strings to qml by using an approach which described in here http://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html. So I have an QObject-derived class names FileNames and it has Q_PROPERTY(QStringList mp3List READ mp3List). So on the constructor of the FileNames I look for the path given in command line, and detect for files with extension .mp3s and push_back those paths to the FileNames::mp3List. And on main.cpp, I instantiate a FileNames object after that instantiate QQuickView object and I pass the FileNames object to Qml side with QQmlContext::setContextProperty member function.
Everything works until that I need also the number of the mp3s in the list to iterate all over with next function which increment index value for the list on qml side. I know I can expose another property for passing count of the mp3List but I ended up with this solution might not be the best native one.
Here is the code that I have wrote;
/* filenames.h */
class FileNames : public QObject {
Q_OBJECT
Q_PROPERTY(QStringList mp3List READ mp3List)
Q_PROPERTY(int mp3ListCount READ mp3ListCount)
public:
explicit FileNames(QObject *parent = 0);
QStringList mp3List() const;
int mp3ListCount() const;
private:
QStringList m_mp3List;
int m_mp3ListCount;
};
/* filenames.cpp */
FileNames::FileNames(QObject *parent) :
QObject(parent), m_mp3ListCount(0)
{
QString path("/home/user/music/");
QDir dirname(path);
QStringList dir = dirname.entryList();
for (const auto &file : dir)
if (file.endsWith(".mp3")) {
m_mp3List.push_back("file://" + path + file);
++m_mp3ListCount;
}
}
QStringList FileNames::mp3List() const
{
return m_mp3List;
}
int FileNames::mp3ListCount() const
{
return m_mp3ListCount;
}
/* main.cpp */
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
FileNames names;
QQuickView view;
view.engine()->rootContext()->setContextProperty("names", &names);
view.setSource(QUrl(QStringLiteral("qrc:///main.qml")));
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.showFullScreen();
return app.exec();
}
/* Playlist.qml */
Item {
id: root
property int index: 0
property MediaPlayer mediaPlayer
property variant list;
property int listCount;
function setIndex(i)
{
console.log("setting index to: " + i);
index = i;
if (index < 0 || index >= listCount) {
index = 0;
mediaPlayer.source = "";
}
mediaPlayer.source = list[index];
}
function next()
{
setIndex(index + 1);
}
function previous()
{
setIndex(index + 1);
}
Connections {
target: root.mediaPlayer
onStopped: {
if (root.mediaPlayer.status == MediaPlayer.EndOfMedia)
{
root.next();
root.mediaPlayer.play();
}
}
}
}
/* main.qml */
Rectangle {
id: root
width: 1024
height: 600
color: "black"
Playlist {
id: playlist
mediaPlayer: player
list: names.mp3List
listCount: names.mp3ListCount
}
MediaPlayer {
id: player
}
VideoOutput {
anchors.fill: parent
source: player
}
}
So does anyone has more native solution to making a "playlist_player" application with Qt?
UPDATE ->
So right now I am using FolderListModel but seems like this class does not work properly without a view. I guess because it works asynchronously. Here is how is my code look like;
/* Playlist.qml */
Item {
id: root
property int index: 0
property MediaPlayer mediaPlayer
property FolderListModel fm
function setIndex(i)
{
index = i;
console.log("setting index to: " + i);
index %= fm.count;
mediaPlayer.source = "file://" + fm.get(index, "filePath");
console.log("setting source to: " + mediaPlayer.source);
}
function next()
{
setIndex(index + 1);
}
function previous()
{
setIndex(index + 1);
}
Connections {
target: root.mediaPlayer
onStopped: {
if (root.mediaPlayer.status == MediaPlayer.EndOfMedia) {
root.next();
root.mediaPlayer.play();
}
}
}
}
/* main.qml */
Rectangle {
id: root
width: 1024
height: 600
color: "black"
property bool onStart: true
Playlist {
id: playlist
mediaPlayer: player
fm: FolderListModel {
id: fm
folder: "file:///home/user/music"
showDirs: false
showDotAndDotDot: false
nameFilters: ["*.mp3"]
property bool ready: count > 0
// startup initialization;
onReadyChanged: if (player.status == MediaPlayer.NoMedia) {
playlist.setIndex(0);
player.play();
}
}
}
MediaPlayer { id: player }
VideoOutput {
anchors.fill: parent
source: player
}
}
Thank you,
Sina.
Using C++ types in QML or defining context properties is perfectly fine when integration with C++ code is needed. C++ naturally extends the possibilities of QML and it is only a matter of understand when a C++ back-end class, or a newly QML type written in C++, is needed. For further discussion about this topic please have a look to this other answer.
Given the answer linked above and the lifetime of your playlist component, using a QML-type instead of a context property would be the right choice. However, as said, QML already provide the right component for you: FolderListModel. In your case it can be easily defined like this:
FolderListModel {
id: folderModel
nameFilters: ["*.mp3"]
showDirs: false
folder: "file:" + /* CPP PROVIDED PATH */
}
and you can use get function to query for the next mp3 in the list, according to the count property.
A great advantage is that FolderListModel does listen for file changes in your folder so that you don't need to check for file addition/removal. Last, but not least, FolderListModel can be easily integrated in a GUI implementation, using it as a model for a ListView. Please see here for an example.
Addendum
As noted by OP the FolderListModel works asynchronously. That's a common behaviour since doing all the checks during component initialisation could lead to a long start-up (especially for directories full of files). There's no property to track when FolderListModel has finished tracking the filesystem, nor it probably makes sense to have one since the filesystem check is continuous. Adding a bool property could help, something like this:
FolderListModel {
id: fm
folder: "..."
showDirs: false
showDotAndDotDot: false
nameFilters: ["*.mp3"]
property bool ready: count > 0
}
When ready is true (because mp3s have been added or the initial scanning has finished) it could be possible to trigger the player. Clearly other properties can be added to improve the behaviour of the model.
The FolderListModel QML Type will do what you need. It supports name filtering, etc.
{New to Qml (quick 2.0 using Qt 5.1 beta) and learning}.
I wanted to know if such an idiom would be possible in Qml:
Below I have objLeftColumn which expects its children to expose a boolean m_bIsSelected and a MouseArea alias m_mouseProperty and uses them to make the collection of such children mutually exclusive, ie., only one of them can be in selected state. The followin works fine but I need to repeat it every time I want and specially if I wanted it for Row etc.
Column {
id: objLeftColumn
property int m_iLastButtonClicked: -1
property int m_iCurrentButtonClicked: -1
onM_iCurrentButtonClickedChanged: {
if(m_iLastButtonClicked != -1) {
objLeftColumn.children[m_iLastButtonClicked].m_bIsSelected = false
}
m_iLastButtonClicked = m_iCurrentButtonClicked
}
Repeater {
id: objLeftColumnRepeater
model: 5
delegate: ABCD {
id: objABCD
m_mouseProperty.onClicked: {
if(m_bIsSelected) {
objLeftColumn.m_iCurrentButtonClicked = index
}
else {
objLeftColumn.m_iLastButtonClicked = -1
objLeftColumn.m_iCurrentButtonClicked = -1
}
}
}
}
}
Can I write a generic objLeftColumn (in a separate qml file) that could arrange the given Items in Column while aslo dealing with exclusivity of their selection?
The idea is instead of giving the component to the delegate right there an then, I'll give it later and for each instantiation of the component (depending on numeric value of model above and below) the delegate: in Repeater should behave similarly.
eg., in psedo code:
in Exclusive.qml:
Column {
id: objLeftColumn
property int m_iLastButtonClicked: -1
property int m_iCurrentButtonClicked: -1
property alias m_delegate: objLeftColumnRepeater.delegate
onM_iCurrentButtonClickedChanged: {
if(m_iLastButtonClicked != -1) {
objLeftColumn.children[m_iLastButtonClicked].m_bIsSelected = false
}
m_iLastButtonClicked = m_iCurrentButtonClicked
}
Repeater {
id: objLeftColumnRepeater
model: 5
onItemAdded: {
//state of item can be manipulated but want to
//add behaviour to the item eg:
/*item {
m_mouseProperty.onClicked: {
//do something
}
}*/
}
}
}
in SomeOther.qml:
Exclusive {
model: 5
delegate: ABCD
}
Exclusive {
model: 9
delegate: DEFG
}
etc..So this way Column in Exclusive is more generic and can be called with any Item assigned to its delegate and will behave similarly. Is this possible in qml
This needs a bit of trickery to be solved, I can think of two ways:
Use the JS connect() function to manually create the connections. Something like this:
Repeater {
id: objLeftColumnRepeater
model: 5
onItemAdded: {
item.m_mouseProperty.onClicked.connect(function() {
console.log("Clicked!");
});
}
}
Wrap the delegate into an Item by using a Loader, and use aConnections element for the connection. Something like this:
property Component delegate
Repeater {
id: objLeftColumnRepeater
model: 5
delegate: Item {
Loader {
id: loader
sourceComponent: delegate
}
Connections {
target: loader.item.m_mouseProperty
onClicked: console.log("Clicked")
}
}