QML SectionScroller and QList<QObject*> - qt

I use QList<QObject*> as a model in my app. As there might be a lot of elements, I decided to use SectionScroller. When I try to scroll using the SectionScroller I'm getting a
Error: Unable to assign [undefined] to QString
What am I doing wrong?
My ListView is:
ListView
{
id: irrview
width: parent.width
model: irregulars.db // QList<QObject*>
anchors.top: caption.bottom
anchors.bottom: parent.bottom
clip: true
section.criteria: ViewSection.FirstCharacter
section.property: "form0"
section.delegate: Item {height: 10; width: parent.width; Text { text: section } } // for testing purposes
delegate: Rectangle
{
/**/
}
}
Thanks
EDIT: more code:
the irregulars header
class IrregularListWrapper : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<QObject*> db READ getdb NOTIFY langChanged)
Q_ENUMS(Language)
public:
enum Language
{
English = 0,
German = 1
};
IrregularListWrapper() : db(0) { setLang(German); }
~IrregularListWrapper() { delete db; }
QList<QObject*> getdb() const { return *db; }
Q_INVOKABLE void changeLang(Language l) { delete db; setLang(l); }
signals:
void langChanged();
protected:
void setLang(Language);
QList<QObject*> * db;
};
and the body of a function
void IrregularListWrapper::setLang(Language l)
{
switch (l)
{
case English:
db = new english;
langName = "English";
break;
case German:
db = new german;
langName = "German";
break;
}
emit langChanged();
}
the classes german, english are like that
class german : public QList<QObject*>
{
public:
german();
};
german::german()
{
append(new IrregularVerb("anfangen", "fing an", "angefangen"));
/*more like that*/
}
and IrregularVerb:
class IrregularVerb : public QObject
{
Q_OBJECT
Q_PROPERTY(QString form0 READ getForm0 NOTIFY formChanged)
Q_PROPERTY(QString form1 READ getForm1 NOTIFY formChanged)
Q_PROPERTY(QString form2 READ getForm2 NOTIFY formChanged)
public:
QString forms[3];
QString getForm0() const { return getForm(0); }
QString getForm1() const { return getForm(1); }
QString getForm2() const { return getForm(2); }
IrregularVerb(QString a, QString b, QString c) { forms[0] = a; forms[1] = b; forms[2] = c; }
protected:
const QString& getForm(const int& ind) const { return forms[ind]; }
signals:
void formChanged();
};
Edit 2: this doesn't work
If I do
QVariantList getdb() const { return QVariant::fromValue(*db); }
IrregularListWrapper.h:24: error: could not convert 'QVariant::fromValue(const T&) [with T = QList<QObject*>; QVariant = QVariant]()' from 'QVariant' to 'QVariantList {aka QList<QVariant>}'
If I remove the star, the error is similar.
EDIT3:
I found out this http://ruedigergad.com/2011/08/22/qml-sectionscroller-vs-qabstractlistmodel/
And found out that irregulars.db.get is undefined
And changed german and english to
class german : public AbstractIrregularList
and
class AbstractIrregularList : public QObject, public QList<QObject*>
{
Q_OBJECT
public:
Q_INVOKABLE QObject* get(int index) {return at(index);}
};
But even now, irregulars.db.get(0) gives error (Result of expression 'irregulars.db.get' [undefined] is not a function.)
Why is happening like that, that the Q_INVOKABLE is not detected? The Q_OBJECT macro is there
/edit5: Even when using QVariant the errors are still there. It can be either treated as QList or as QObject*.

If I am not wrong, you should use QVariantList instead QList<SomeClass> to expose list of elements from C++ to QML.
It should solve problem.
Supported types in QML
Try making code to look like this (in irregulars header):
Q_PROPERTY(QVariantList db READ getdb NOTIFY langChanged)
//...
QVariantList getdb() const {/*convert db to QVariantList*/ return converted_db;}
Or if you want to code look like from links you gave:
Q_PROPERTY(QVariantList db READ getdb NOTIFY langChanged)
//...
QVariant getdb() const {return QVariant::fromValue(db);}

Related

Is it possible to create binding proxy in qt qml?

i have a cpp struct which have so much fields
struct CloudMusicSongList{ ... };
and i want use it in qml in order input some infomation to it's instance, but i don't want to create a qobject derived class,and create a lot of qproperty ...
i seache in google and get this code snippt from this blog
class ListItem : public QObject
{
Q_OBJECT
public :
ListItem(QObject *parent = 0) : QObject(parent) {}
virtual ~ListItem() {}
virtual int id() const = 0;
virtual QVariant data(int role) const = 0;
virtual QHash<int, QByteArray> roleNames() const = 0;
virtual void triggerItemUpdate() {emit dataChanged();}
signals:
void dataChanged();
};
i just try following code but even not print called data method
defined.h
template <typename T>
class bindingProxy : public bindingProxyBase
{
public:
bindingProxy(QObject* parent = 0)
: bindingProxyBase(parent)
{
}
T tm;
virtual ~bindingProxy()
{
}
virtual QVariant data(int role)
{
qDebug() << "called data method";
QVariant qv;
auto fucs = Prism::Reflection::get_md<T>();
if (role == Qt::DisplayRole)
fucs[0].getFunc(tm, 0, qv);
else
fucs[role].getFunc(tm, role, qv);
return qv;
}
QHash<int, QByteArray> roleNames() const
{
static QHash<int, QByteArray> roles{};
if (roles.count() == 0)
{
for (Prism::Reflection::PrismMetaData<T>& item : Prism::Reflection::get_md<T>())
{
roles[item.offset] = item.name;
}
}
return roles;
}
bool setData(QVariant& value, int role = Qt::EditRole)
{
Prism::Reflection::get_md<T>()[role].setFunc(tm, role, value);
triggerItemUpdate();
return true;
}
};
bodyViewModel.cpp
#include ....
...
..
Q_DECLARE_METATYPE(bindingProxy<CloudMusicSongList>*);
class BodyViewModel : public QObject
{
Q_PROPERTY(bindingProxy<CloudMusicSongList>* editSongListModel READ editSongListModel WRITE setEditSongListModel NOTIFY editSongListModelChanged)
...
...
...
uing.qml
Button {
id:btn_tst
text: BodyViewModelCpp.editSongListModel.name + "hbb"
onClicked: BodyViewModelCpp.editSongListModel.name = "3232"
}
button look like this image after run app rung
i don't know whether it is the X Y problem.
somebody can help? thanks
i want create a bindingProxy template class which twoway bindable in qml
i think it should equivalence dynamicObject in wpf
Based on CloudMusicSongList I'm assuming the thing that you have is a list/array. There are quite a number of ways of exposing this to QML.
QQmlListProperty - https://doc.qt.io/qt-6/qqmllistproperty.html
QAbstractListModel - https://doc-snapshots.qt.io/qt6-dev/qabstractlistmodel.html
QVariant, QVariantList, and/or QVariantMap - https://doc-snapshots.qt.io/qt6-dev/qvariant.html
QObject / Q_OBJECT - create a new QML component and register it
Q_GADGET - register your C++ structure (good for access the class in a signal, but, QML/JS cannot copying it into a Javascript object, only good for the duration of the signal)
QQmlListProperty is a list of QObjects. It requires the underlying QObject be registered, but, allows you to create/manage and walk through a list of the results. The list is owned, usually by another QObject. e.g. we could have a QmlFile object, then implement a QmlFileSystem object that can return a list of QmlFiles.
QAbstactListModel can be used in places where a model is required, e.g. ListView.
QVariant, QVariantList and QVariantMap is the simplest way of creating a Javascript object in QML. The advantage is you do not have to register anything, but, the disadvantage is you do not get intellisense.
QVariant getBooks() {
QVariantList books;
QVariantMap book1;
book1["title"] = QString("Lord of the Rings");
QVariantMap book2;
book2["title"] = QString("The Hobbit");
books.append(book2);
return books;
// results in QML/JS:
// [ { "title": "Lord of the Rings" },
// { "title" : "The Hobbit" } ]
}

Set character format for a QML TextArea using QTextCursor

I'm trying to create a very simple rich text editor component in QML. The requirements are that the user must be able to quickly format individual words inside a textbox separately. So for example, the user could press "Ctrl+B" in the middle of typing and from that point onward the text becomes bold, when the user press "Ctrl+B" again the subsequently typed text has a normal font weight:
So looking around I found that there is an official example provided by the Qt company to do this. So I extrapolated this to a very basic app:
// DocumentHandler.cpp
#pragma once
#include <QObject>
#include <QQuickTextDocument>
#include <QTextCharFormat>
class DocumentHandler : public QObject {
Q_OBJECT
Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged)
Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged)
Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged)
Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged)
Q_PROPERTY(bool modified READ modified WRITE setModified NOTIFY modifiedChanged)
Q_PROPERTY(bool bold READ bold WRITE setBold NOTIFY boldChanged)
public:
QQuickTextDocument *document() const;
void setDocument(QQuickTextDocument *newDocument);
int cursorPosition() const;
void setCursorPosition(int newCursorPosition);
int selectionStart() const;
void setSelectionStart(int newSelectionStart);
int selectionEnd() const;
void setSelectionEnd(int newSelectionEnd);
bool modified() const;
void setModified(bool newModified);
bool bold() const;
void setBold(bool newBold);
signals:
void documentChanged();
void cursorPositionChanged();
void selectionStartChanged();
void selectionEndChanged();
void modifiedChanged();
void boldChanged();
private:
void mergeFormatOnWordOrSelection(const QTextCharFormat & format);
void reset();
QTextCursor textCursor() const;
QTextDocument* textDocument() const;
QQuickTextDocument * m_document = nullptr;
int m_cursorPosition = -1;
int m_selectionStart = 0;
int m_selectionEnd = 0;
};
// DocumentHandler.cpp
#include "documenthandler.h"
#include <QTextCursor>
QQuickTextDocument *DocumentHandler::document() const
{
return m_document;
}
void DocumentHandler::setDocument(QQuickTextDocument *newDocument)
{
if (m_document == newDocument)
return;
if(m_document)
m_document->textDocument()->disconnect(this);
m_document = newDocument;
if(m_document){
QObject::connect(m_document->textDocument(), &QTextDocument::modificationChanged,
this, &DocumentHandler::modifiedChanged);
}
emit documentChanged();
}
int DocumentHandler::cursorPosition() const
{
return m_cursorPosition;
}
void DocumentHandler::setCursorPosition(int newCursorPosition)
{
if (m_cursorPosition == newCursorPosition)
return;
m_cursorPosition = newCursorPosition;
reset();
emit cursorPositionChanged();
}
int DocumentHandler::selectionStart() const
{
return m_selectionStart;
}
void DocumentHandler::setSelectionStart(int newSelectionStart)
{
if (m_selectionStart == newSelectionStart)
return;
m_selectionStart = newSelectionStart;
emit selectionStartChanged();
}
int DocumentHandler::selectionEnd() const
{
return m_selectionEnd;
}
void DocumentHandler::setSelectionEnd(int newSelectionEnd)
{
if (m_selectionEnd == newSelectionEnd)
return;
m_selectionEnd = newSelectionEnd;
emit selectionEndChanged();
}
bool DocumentHandler::modified() const
{
return m_document && m_document->textDocument()->isModified();
}
void DocumentHandler::setModified(bool newModified)
{
if (m_document)
m_document->textDocument()->setModified(newModified);
}
void DocumentHandler::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
{
QTextCursor cursor = textCursor();
if (!cursor.hasSelection())
cursor.select(QTextCursor::WordUnderCursor);
cursor.mergeCharFormat(format);
}
void DocumentHandler::reset()
{
emit boldChanged();
}
QTextCursor DocumentHandler::textCursor() const
{
QTextDocument* doc = textDocument();
if(!doc)
return QTextCursor();
QTextCursor cursor = QTextCursor(doc);
if(m_selectionStart != m_selectionEnd){
cursor.setPosition(m_selectionStart);
cursor.setPosition(m_selectionEnd, QTextCursor::KeepAnchor);
}
else{
cursor.setPosition(m_cursorPosition);
}
return cursor;
}
QTextDocument *DocumentHandler::textDocument() const
{
return m_document ? m_document->textDocument() : nullptr;
}
bool DocumentHandler::bold() const
{
auto cursor = textCursor();
if(cursor.isNull())
return false;
return cursor.charFormat().fontWeight() == QFont::Bold;
}
void DocumentHandler::setBold(bool newBold)
{
QTextCharFormat format;
format.setFontWeight(newBold ? QFont::Bold : QFont::Normal);
mergeFormatOnWordOrSelection(format);
emit boldChanged();
}
// main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import DocumentHandler 1.0
ApplicationWindow {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
color: "lightpink"
header: ToolBar {
id: appToolbar
ToolButton {
text: "Bold"
checkable: true
checked: documentHandler.bold
onClicked: {
documentHandler.bold = !documentHandler.bold
}
}
}
DocumentHandler {
id: documentHandler
document: textInput.textDocument
cursorPosition: textInput.cursorPosition
selectionStart: textInput.selectionStart
selectionEnd: textInput.selectionEnd
}
TextArea {
id: textInput
anchors.fill: parent
font.pixelSize: 30
wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere
focus: true
selectByMouse: true
persistentSelection: true
textFormat: Qt.RichText
}
}
If you try to run this app, type something like "hello" and the press the "Bold" button the app correctly makes the "hello" bold. However, there is a problem I'm struggling to get past:
if you click on the button when the textarea is empty, then typing doesn't give you bold text. In fact, it seems that it is not possible to change the font weight of the textarea prior to having some text in it. Also, if you type a word, then press space and then click the button: nothing happens.
Anyone got any clue on what's the root cause of this behaviour?

How to properly link C++ model to QML front end

here is my setup:
First is the model class, this stores a pointer to the data of type CompetitionsList. We can see that it implements the necessary basic functions when deriving from QAbstractListModel, and it uses the QML_ELEMENT macro to expose the model to the QML type system:
class CompetitionsListModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(CompetitionsList* list READ list WRITE setList)
QML_ELEMENT
public:
explicit CompetitionsListModel(QObject *parent = nullptr);
enum {
NameRole = Qt::ItemDataRole(),
IdRole
};
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
CompetitionsList *list() const;
void setList(CompetitionsList *list);
virtual QHash<int, QByteArray> roleNames() const;
private:
CompetitionsList* m_list; \\ <--- ptr to data
};
Then we have the data class. This class is also a QML_ELEMENT, and it has a userid property. This is used to populate the QVector<listItem> by requesting data from a RESTful http server.
The two signals preItemAppended() and postItemAppended() are emitted when data is added to the QVector<listItem>. These signals are then connected to the Model as a way of notifying the model it must create a new row in the list. The important function here is populateCompetitions, which I explain below.
struct listItem {
QString competitionName;
QString competitionId;
};
class CompetitionsList : public QObject
{
Q_OBJECT
Q_PROPERTY(QString userId READ getUserId WRITE setUserId)
QML_ELEMENT
public:
explicit CompetitionsList(QObject *parent = nullptr);
void populateCompetitions(const QString& userId);
QVector<listItem> items();
QString getUserId() const;
void setUserId(const QString &value);
signals:
void preItemAppended();
void postItemAppended();
void errorPopulatingData();
private:
QVector<listItem> m_competitions;
QString userId;
};
The populateCompetitions function looks as so:
void CompetitionsList::populateCompetitions(const QString &userId)
{
qDebug() << "initialising data";
HttpClient* client = new HttpClient();
client->getUserCompetitions(userId);
connect(client, &HttpClient::getUserCompetitionsResult, [=](const QByteArray& reply){
QJsonDocument _reply = QJsonDocument::fromJson(reply);
if(_reply["data"].isNull()) {
emit errorPopulatingData();
}
else {
const QJsonArray competitions = _reply["data"]["competitions"].toArray();
for(const QJsonValue& competition : competitions) {
emit preItemAppended();
listItem item{competition["competition-name"].toString(), competition["competition-id"].toString()};
m_competitions.append(std::move(item));
emit postItemAppended();
}
}
});
}
It requests data from the database, and then stores it locally.
Then finally, the QML model which ties it all together:
ListView {
id: view
implicitHeight: 1920
implicitWidth: 1080
clip: true
model: CompetitionsListModel {
list: CompetitionsList {
id: compList
}
Component.onCompleted: { compList.userId = "6033f377257e8630ed13299e" } //<-- calls the populateCompetitions function
}
delegate: RowLayout {
Text {
text: qsTr(model.name + ":::" + model.id)
}
}
}
We can see that it has a ListView element, with a model of type CompetitionsListModel and a list of type CompetitionsList. Once the model component is created, we set the userId of the list, this in turn calls the populateCompetitions function which sets up the data for the model to use as seen above.
Unfortunately this doesnt anything when I run the code. Blank screen. Nada. I was wondering if anyone has an insight as to what might be causing this based on the code provided. Ive been at it for so long and it just inst being nice.
I think you need to call qmlRegisterType or UncreatableType on your CompetitionsListModel :
https://doc.qt.io/qt-5/qqmlengine.html#qmlRegisterType

Making window previews in qt using QSCreen::grabWindow() function

I want to make a screen caster application that captures a window or a screen directly to QQuickImage. To do so I made this header which has a thread object which frequently updates screen shots when signal received (sorry file was written in a poor language)
class OtherThread : public QThread
{
Q_OBJECT
QString oldWritingFile = "filename.png";
bool loaded = true;
public:
int winId = 0;
OtherThread(QObject *parent = nullptr):QThread(parent){};
~OtherThread(){
if(isRunning()){
requestInterruption();
wait();
}
};
void setParams(int id){
winId = id;
}
void knowLoaded(){
loaded =true;
}
signals:
void fileCacheChanged(QString);
protected:
void run() override{
while (!isInterruptionRequested()) {
if(winId != 0 && loaded){
bool success;
loaded = false;
if(oldWritingFile == QString::number(winId)+".png"){
if(qApp->primaryScreen()->grabWindow(winId).save(QString::number(winId)+"file.png")){
oldWritingFile = QString::number(winId)+"file.png";
success = true;
}else{success=false;}
}else{
if(qApp->primaryScreen()->grabWindow(winId).save(QString::number(winId)+".png")){
oldWritingFile = QString::number(winId)+".png";
success = true;
}else{success = false;}
}
emit fileCacheChanged(oldWritingFile);
}
}
};
};
class Controller : public QObject
{
Q_OBJECT
QString oldWritingFile;
public:
Controller(QObject *parent = nullptr):QObject(parent) {}
virtual ~Controller() {}
void changeFile(QString message){
oldWritingFile = message;
emit newFile(message);
};
public slots:
void startThread(){
OtherThread *thread =new OtherThread;
connect(thread, &OtherThread::fileCacheChanged, this, &Controller::changeFile);
connect(this, &Controller::stop, thread, &OtherThread::requestInterruption);
connect(this, &Controller::changePrint, thread, &OtherThread::setParams);
connect(this, &Controller::loaded, thread, &OtherThread::knowLoaded);
thread->start();
}
QString getLoadedFile(){
if(oldWritingFile != NULL){
return "file:./"+oldWritingFile;
}else{
return "file:./index.png";
}
}
signals:
void stop();
void changePrint(int id);
void newFile(QString);
void loaded();
};
and with my qml image i did this
Image {
id: image
anchors.fill: parent
source: "images/kalaripayattu.svg"
fillMode: Image.PreserveAspectFit
cache:false
Component.onCompleted: {
justThread.newFile.connect(updateFunction)
}
function updateFunction(filename){
source = "file:./"+filename;
justThread.loaded()
}
}
JustThread{
signal stopThread
id:justThread
onStopThread: {
stop()
}
Component.onCompleted: {
startThread()
changePrint(id)
}
}
Component.onCompleted:{
applicationWindow.closing.connect(justThread.stopThread)
}
but it has a really horrible fps.
can anything be done to increase fps?
Is there any easy way to do this?
Image is not really made for displaying frequently changing images. What you want is VideoOutput.
Here's what I would do :
You need to create a QObject derived class instance and set it as the source of the VideoOutput : http://doc.qt.io/qt-5/qml-qtmultimedia-videooutput.html#source-prop
Your class needs to have a Q_PROPERTY(QAbstractVideoSurface* videoSurface READ videoSurface WRITE setVideoSurface NOTIFY videoSurfaceChanged)
In your setVideoSurface method, you need to call the start method of QAbstractVideoSurface with a correct format (your size and the pixelformat, pixelformat should be QVideoFrame::Format_ARGB32 for desktop I guess).
And then when you want to update the VideoOutput (via a QTimer for example), you call the present method of QAbstractVideoSurface with a QVideoFrame you constructed from the QPixmap you got in QScreen::grabWindow.
For that, you could convert the QPixmap to QImage with toImage and then convert it to QVideoFrame with theQVideoFrame::QVideoFrame(const QImage &image) constructor.

Q_PROPERTY not shown

I'm using Q_PROPERTY with QML. My code is:
using namespace std;
typedef QString lyricsDownloaderString; // this may be either std::string or QString
class lyricsDownloader : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(QString lyrics READ lyrics NOTIFY lyricsChanged)
Q_INVOKABLE virtual short perform() = 0;
inline void setData(const string & a, const string & t); // set artist and track
Q_INVOKABLE inline void setData(const QString & a, const QString & t); // for QStrings
Q_INVOKABLE inline bool anyFieldEmpty(); // check whether everything is filled
inline QString lyrics()
{
return lyrics_qstr;
}
/*some more data*/
signals:
void lyricsChanged(QString);
};
class AZLyricsDownloader : public lyricsDownloader
{
Q_OBJECT
public:
AZLyricsDownloader() : lyricsDownloader("", "") {}
AZLyricsDownloader(const string & a, const string & t) : lyricsDownloader(a, t) {}
Q_INVOKABLE short perform();
//Q_INVOKABLE inline void setData(const string & a, const string & t);// set artist and track
protected:
/*some more data*/
};
and one of the Pages in QML is
import QtQuick 1.1
import com.nokia.meego 1.0
import com.nokia.extras 1.0
Page
{
id: showLyricsPage
tools: showLyricsToolbar
Column
{
TextEdit
{
id: lyricsview
anchors.margins: 10
readOnly: true
text: azdownloader.lyrics
}
}
Component.onCompleted:
{
azdownloader.perform()
busyind.visible = false
}
BusyIndicator
{id: busyind /**/ }
ToolBarLayout
{id: showLyricsToolbar/**/}
// Info about disabling/enabling edit mode
InfoBanner {id: editModeChangedBanner /**/}
}
azdownloader is an AZLyricsDownloader object
The codes runs correctly in C++, the function returns the text which should be in TextEdit.
But unfortunately, the TextEdit is blank. No text is shown there.
there is no body for the signal, but AFAIK signal doesn't need it.
If I use
Q_PROPERTY(QString lyrics READ lyrics CONSTANT)
the result is the same.
What am I doing wrong?
When you change the value of the lyrics property in C++ code, you have to send the NOTIFY signal of the property (here void lyricsChanged();) :
this->setProperty("lyrics", myNewValue);
emit lyricsChanged();
In this case, QML should update the value of the property.

Resources