QUiLoader: requirements for loading .ui file with custom widgets? - c++

I've created a UI using Qt5-Designer which I load at runtime by calling
QUiLoader().load(qfile_object, this);
Works like charm but now I've promoted some QLabel elements to a widget class MyQLabel with is derived from QLabel.
When I now try to load the UI I get a warning for each promoted widget:
"QFormBuilder was unable to create a custom widget of the class 'MyQLabel'; defaulting to base class 'QLabel'."
The class looks like this:
class MyQLabel : public QLabel {
Q_OBJECT
public:
MyQLabel(QWidget *parent = nullptr) : QLabel(parent) {}
};
It's been auto-moc'ed and linked against my executable.
I have the feeling that somehow I have to tell QUiLoader about my class before trying to use it but I don't know how..
Do I have to create a plugin for this? Is there a way to reproduce, what to QUiLoader does to examine it?

You need to create your own derived version of QUiLoader, and provide an implementation of the factory method QUiLoader::createWidget that can create your widgets.
You could factor this out into a plugin that gets loaded by the QUiLoader. It would have to implement a QDesignerCustomWidgetInterface instance. See the Custom Widget Plugin Example for a complete example of a plugin.
// https://github.com/KubaO/stackoverflown/tree/master/questions/uiloader-custom-37775472
#include <QtUiTools>
#include <QtWidgets>
const char uiData[] =
"<ui version=\"4.0\"><class>Widget</class><widget class=\"MyWidget\" name=\"Widget\">\n"
"<property name=\"windowTitle\" ><string>Widget</string></property>\n"
"</widget><pixmapfunction></pixmapfunction><resources/><connections/>\n"
"</ui>";
class MyWidget : public QLabel
{
Q_OBJECT
bool m_closed = false;
public:
MyWidget(QWidget* parent = 0) : QLabel("This is MyWidget", parent) {}
bool isClosed() const { return m_closed; }
void closeEvent(QCloseEvent *) Q_DECL_OVERRIDE { m_closed = true; }
};
class MyUiLoader : public QUiLoader {
public:
MyUiLoader(QObject * parent = 0) : QUiLoader(parent) {}
QWidget * createWidget(const QString & className, QWidget * parent = 0,
const QString & name = QString()) {
if (className == "MyWidget") {
MyWidget * w = new MyWidget(parent);
w->setObjectName(name);
return w;
}
return QUiLoader::createWidget(className, parent, name);
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QBuffer buf;
buf.setData(uiData, sizeof(uiData));
MyUiLoader uiLoader;
auto uiMain = qobject_cast<MyWidget*>(uiLoader.load(&buf));
uiMain->show();
return app.exec();
}
#include "main.moc"

Related

How to access QMainWindow from another class

// mainwindow.h
#include "ui_MainWindow.h"
#include "parseTextFile.h"
class MainWindow:public QMainWindow
{
Q_OBJECT
public:
MainWindow(void);
private:
Ui_mainWindow ui;
parseFile *fileParse;
public slots:
int onOkButtonClick();
};
// mainwindow.cpp
MainWindow::MainWindow(void)
{
ui.setupUi(this);
connect(ui.OkButton,SIGNAL(clicked()),this,SLOT(onOkButtonClick()));
}
int MainWindow::onOkButtonClick()
{
fileParse = new parseFile(this);
fileParse->parseTextFile();
return 0;
}
int main(int argc,char* argv[])
{
QApplication app(argc,argv);
MainWindow *mainWindow = new MainWindow();
mainWindow->show();
return app.exec();
}
// parseTextFile.h
class parseFile
{
public:
parseFile(QWidget *parent =0);
~parseFile();
int parseTextFile( );
};
// parseTextFile.cpp
#include "parseTextFile.h"
#include <QMessageBox>
parseFile::parseFile(QWidget *parent)
{
}
parseFile::~parseFile()
{
}
int parseFile::parseTextFile( )
{
QMessageBox::information(this,"a","b");
return 0;
}
I can able to access parseTextFile method but i am getting error in QMessageBox. Is it the right way?
How to print QMessageBox in parseTextFile class?
anything needs to add in parseTextFile constructor?
Pass this to QMessageBox::information as a parent is not a valid argument because parseFile is not a QWidget derived class.
For simple, just pass nullptr instead of this :
QMessageBox::information(nullptr ,"a","b");
In this case, the message box belongs to no one. It's will be closed by the user or when the application exits.
PS: you should release the memory after using fileParse to avoid memory leaks.
There are several ways to like with the main window (static pointer, singleton, top widgets enumeration,etc.) but the simplest one, that I thinks you intend to do, is to make parseFile class as QObject derived class and QMainWindow its parent.
class parseFile: public QObject
{
Q_OBJECT
public:
parseFile(QWidget *parent =0);
~parseFile();
int parseTextFile();
};
parseFile::parseFile(QWidget *parent)
:QObject(parent)
{
}
int parseFile::parseTextFile()
{
QWidget * parentWidget = qobject_cast<QWidget *>( parent() ); //<-- QMainWindow instance
QMessageBox::information( parentWidget, "a", "b");
return 0;
}
int MainWindow::onOkButtonClick()
{
fileParse = new parseFile(this);
fileParse->parseTextFile( );
//fileParse should be released by:
//fileParse->deleteLater();
return 0;
}

Linking a QWidget to another QWidget new created

I am try to, by pressing a button in the main QWidget, to create a new QWidget. In that new created QWidget I want to have a button connected to a slot of the main QWidget.
class UI : public QWidget
{
public:
UI(){connection();};
private:
QPushButton* all = new QPushButton{ "ALL" };
void connection(){
QObject::connect(all,QPushButton::clicked,[](){
SmallGUI* s=new SmallGUI{};
s->show();
});
}
void something(){
//something
}
and the second class
class SmallGUI1 :
public QWidget
{
public:
SmallGUI(){connection();};
private:
QPushButton* N =new QPushButton;
void connection(){
//to connect N to something()
}
I want to connect N to something() .
Before we start, there are some other problems with you code.
Note that in your second class, the constructor is not named the same as the class, which will cause some... Problems.
You also forgot to put a parent for your buttons (which may thus cause some unexpected results) AND for your Widgets (which is again not a good idea).
So, that being said, let us get to the main topic.
I tend to only put prototypes and declare the attributes in the .h file to make the code clearer, but you may of course adapt it to your needs or to your own programming convention.
There are several ways to do something like this, but the simplest one should look like this :
SmallGUI1.h :
#include "UI.h" //The file in which the class UI is declared
//OR :
//class UI; //If you need to include this file in UI.h
class SmallGUI1 : public QWidget{
Q_OBJECT //Q_OBJECT macro, check the doc for more explainations about it
public:
explicit SmallGUI1(UI *parent = nullptr); //Explicit means that this constructor cannot be used for implicit casts
~SmallGUI1();//Destructor needed because I only put the constructor above
private:
QPushButton* N; //Not very good looking to initialize attributes in the .h in my opinion, but works fine.
}
SmallGUI1.cpp :
SmallGUI1::SmallGUI1(UI *parent) : QWidget(parent){
N = new QPushButton(tr("Some text on the button") , this); //tr to enable translation on this string
//************* If I understood your question correctly, this is what you are looking for *************
connect(N , &QPushButton::clicked , parent , &UI::doSomething); //Select the signal you want
/*
Some code here
*/
show();
}
SmallGUI1::~SmallGUI1(){qDeleteAll(children());}
UI.h :
class UI : public QWidget{
Q_OBJECT
public:
explicit UI(QWidget *parent = nullptr);
~UI();
private:
QPushButton* all;
private slots :
void createSmallGUI1();
void doSomething();
}
UI.cpp :
#include "SmallGUI1.h"
UI::UI(QWidget *parent) : QWidget(parent){
all = new QPushButton(tr("ALL") , this);
connect(all , &QPushButton::clicked , this , &UI::createSmallGUI1);
/*
Some code here
*/
}
UI::~UI(){qDeleteAll(children());}
void UI::createSmallGUI1(){SmallGUI1 *gui = new SmallGUI1(this);}
void UI::doSomething(){
/*
Clever code here
*/
}
You can define the second widget as a child of the main widget to make things easier:
class UI : public QWidget {
...
private:
SmallGUI* s;
...
and then initialize it in the UI constructor, along with your all button. You can initially hide the child widget or disable it:
UI() {
all = new QPushButton{"ALL", this};
setWindowTitle("UI"); // just for clarification
s = new SmallGUI(this);
s->hide();
connection();
};
and 'show' it with button clicked signal
connect(all, &QPushButton::clicked, s, &SmallGUI::show);
Doing so gives you the option to connect the clicked signal of your N button to the something function in the parent class
connect(s->N, &QPushButton::clicked, this, &UI::something);
The complete program would be as follows,
#include <QApplication>
#include <QMessageBox>
#include <QPushButton>
#include <QWidget>
class SmallGUI : public QWidget {
public:
SmallGUI(QWidget* parent) : QWidget(parent) {
N = new QPushButton{"btn2", this};
connection();
};
QPushButton* N;
private:
void connection(){};
};
class UI : public QWidget {
public:
UI() {
all = new QPushButton{"ALL", this};
setWindowTitle("UI"); // just for clarification
s = new SmallGUI(this);
s->hide();
connection();
};
private:
SmallGUI* s;
QPushButton* all;
void connection() {
connect(all, &QPushButton::clicked, s, &SmallGUI::show);
connect(s->N, &QPushButton::clicked, this, &UI::something);
}
void something() { QMessageBox::information(this, "Hello", "Hello"); }
};
int main(int argc, char* argv[]) {
QApplication a(argc, argv);
UI w;
w.show();
return a.exec();
}
It is not good idea to connect to parent's slots from "nested" class, since SmallGUI1 will be tied to class UI.
Here is better solution, I think:
class UI : public QWidget
{
public:
UI(){connection();};
private:
QPushButton* all = new QPushButton{ "ALL" };
void connection(){
QObject::connect(all,QPushButton::clicked,[](){
SmallGUI1* s=new SmallGUI1;
connect(s,&USmallGUI1::button_clicked,this,&UI::something);
s->show();
});
}
void something(){
//something
}
And SmallGUI1 class:
class SmallGUI1 :
public QWidget
{
public:
SmallGUI1(){connection();};
signals:
void button_clicked();
private:
QPushButton* N;
void connection(){
//to connect N to something()
N = new QPushButton;
connect(N,&QPushButton::clicked,this,&SmallGUI1::button_clicked)
}
This way, you are connecting QPusButton::clicked signal from SmallGUI1 to the signal SmallGUI1::button_clicked(). Dont need to implement additional slot, just connect signal to signal.
And in UI you are connecting button_clicked() signal to the slot dosomething()
DONT FORGET THE CONSTRUCTOR OF SmallGUI1! In your code, SmallGUI() will be just a method, which will not be called when SmallGUI1 is instantiated, and you have to call it by yourself.

How to implement rich text logic on QML TextEdit with QSyntaxHighlighter class in Qt?

I have a TextEdit in my QML file and I have a QSyntaxHighlighter C++ class. I want to specify the highlighting logic in the C++ class and apply it to the TextEdit, but I am not sure how to make the connection between the QML object and the C++ class. Can you also please provide some sample code? I couldn't find how to implement it with the Qt documentation.
You can use TextEdit::textDocument, which holds an instance of QQuickTextDocument, to gain access to the underlying QTextDocument that you can pass to QSyntaxHighlighter constructor.
In case someone needs a more detailed explanation for this.
First, I created a Q_PROPERTY inside a custom C++ class.
Q_PROPERTY(QQuickTextDocument* mainTextEdit READ mainTextEdit WRITE setMainTextEdit NOTIFY mainTextEditChanged)
Then I assign textEdit.textDocument to the Q_PROPERTY in the QML.
customClass.mainTextEdit = textEdit.textDocument
Then I call initHighlighter() (the function has to be Q_INVOKABLE) in my QML which calls the constructor of my highlighter class and passes it the text document of the textEdit.
void initHighlighter()
{
Highlighter *highlighter;
highlighter = new Highlighter(m_mainTextEdit->textDocument());
}
Note: The custom highlighter class needs to be derived from QSyntaxHighlighter.
Sample implementation:
HighlightComponent.h
class HighlightComponent : public QObject
{
Q_OBJECT
//#formatter:off
Q_PROPERTY(QString text
READ getText
WRITE setText
NOTIFY textChanged)
//#formatter:on
using inherited = QObject;
public:
explicit HighlightComponent(QObject* parent = nullptr);
static void registerQmlType();
const QString& getText()
{
return _text;
}
void setText(const QString& newText)
{
if (newText != _text)
{
_text = newText;
emit textChanged();
}
}
Q_INVOKABLE void onCompleted();
signals:
void textChanged();
private:
std::unique_ptr<QSyntaxHighlighter> _highlight;
QString _text = "";
};
HighlightComponent.cpp
HighlightComponent::HighlightComponent(QObject* parent)
: inherited(parent)
{
}
void HighlightComponent::onCompleted()
{
auto property = parent()->property("textDocument");
auto textDocument = property.value<QQuickTextDocument*>();
auto document = textDocument->textDocument();
//TODO init here your QSyntaxHighlighter
}
void HighlightComponent::registerQmlType()
{
qmlRegisterType<HighlightComponent>("com.HighlightComponent", 1, 0, "HighlightComponent");
}
main.cpp
int main(int argc, char* argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
HighlightComponent::registerQmlType();
engine.load(QUrl(QStringLiteral("qrc:/view/main.qml")));
return app.exec();
}
Sample QML
TextArea {
id: testTextArea
text: testTextArea.text
//inputMethodHints: Qt.ImhNoPredictiveText
onTextChanged: {
testTextArea.text = text
}
HighlightComponent {
id: testTextArea
Component.onCompleted: onCompleted()
}
}
Links:
https://wiki.qt.io/How_to_Bind_a_QML_Property_to_a_C%2B%2B_Function
http://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html
http://wiki.qt.io/Spell-Checking-with-Hunspell
http://doc.qt.io/qt-5/qtqml-cppintegration-interactqmlfromcpp.html
http://doc.qt.io/qt-5/qtwidgets-richtext-syntaxhighlighter-example.html

QWidget Application w/QML and Registered Types

I have a QWidget application that makes use of QML. I have a class that I'm using to expose some of our organizations utility functions.
I have boiled the problem down to the following code (I'll explain my problem below the code):
First, here is the main.cpp file (I've excluded most of the includes for brevity):
#include "main.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}
Here is the included main.h:
class MyUtils : public QObject
{
Q_OBJECT
public:
MyUtils(QObject* parent = nullptr)
: QObject(parent)
{
}
virtual ~MyUtils() = default;
Q_INVOKABLE QString doSomething()
{
return QString("I did something!");
}
static QObject* MyUtilsProvider(QQmlEngine *engine, QJSEngine *scriptEngine)
{
Q_UNUSED(engine)
Q_UNUSED(scriptEngine)
qDebug() << "MyUtils Invoked!";
return new MyUtils();
}
};
class MyView : public QQuickWidget
{
Q_OBJECT
public:
MyView(QWidget* parent = nullptr)
: QQuickWidget(parent)
{
setResizeMode(QQuickWidget::SizeRootObjectToView);
setSource(QUrl("qrc:/main.qml"));
}
virtual ~MyView() = default;
};
class MainWindow : public QMainWindow
{
Q_OBJECT
QTabWidget _tabView;
public:
MainWindow(QWidget * parent = 0)
: QMainWindow(parent)
{
qmlRegisterSingletonType<MyUtils>("MyUtilities", 1, 0, "myutils", &MyUtils::MyUtilsProvider);
setCentralWidget(&_tabView);
_tabView.addTab(new MyView(), "Tab 1");
}
};
And last, here is my QML file:
import QtQuick 2.1
import MyUtilities 1.0
Rectangle
{
Text
{
text: myutils.doSomething()
anchors.centerIn: parent
}
}
What I am trying to do is register the MyUtils class as a singleton that I can then include in my QML and use. The problem is that when I run this, I get the following message from the Application's output:
QML debugging is enabled. Only use this in a safe environment.
Qml debugging is enabled. Only use this in a safe environment!
qrc:/main.qml:8: ReferenceError: myutils is not defined
I have tried putting qmlRegisterSingletonType in main(), before the instantiation of the QApplication object (and in various other places for giggles) but so far I have not been able to get this to work.
I have noticed that if I put a breakpoint or qDebug() message in the MyUtils::MyUtilsProvider method, it never gets called. This leads me to think that maybe my MyView class is using a different QQmlEngine object than the one in which the qmlRegisterSingletonType is registering the singleton with. But if that's the case, then I don't know how to get that engine to then pass to the MyView constructor.
Can someone please tell me what I am doing wrong and how I can get this to work?
Thank you!
QML component names must begin with capital letters:
qmlRegisterSingletonType<MyUtils>("MyUtilities", 1, 0, "Myutils",
&MyUtils::MyUtilsProvider);
and thus
text: Myutils.doSomething()

QML callback from C++ with custom type as parameter

I would like to be able to call a QML function from C++ with an instance of a custom class as a parameter and then manipulate the instance from QML.
Here is what I did so far :
Data.h
class Data : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
public :
Data() : QObject(), _text("Foo") { }
virtual ~Data() { }
Data(const Data & other) { _text = other._text; }
QString text() const { return _text; }
void setText(const QString & text) { _text = text; }
private :
QString _text;
};
Q_DECLARE_METATYPE(Data);
Main.cpp
#include "Data.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Data callBackData;
QQmlEngine engine;
QQmlComponent rootComponent(&engine, QUrl::fromLocalFile("CallBack.qml"));
QObject * rootObj = rootComponent.create();
QMetaObject::invokeMethod(rootObj, "callMeBack",
Q_ARG(QVariant, QVariant::fromValue(callBackData)));
return app.exec();
}
CallBack.qml
import QtQuick 2.0
Item {
function callMeBack(data) {
console.log(data.text)
}
}
The console outputs "Undefined". Did I do something wrong ?
When changing the function body to console.log(data) it outputs "QVariant(Data)" so why can't I access the text property of data ?
I tried registering Data as a QML type using qmlRegisterType<Data>(); but this does not change anything.
Try pass a QObject pointer instead:
Data *callbackData = new Data;
...
QMetaObject::invokeMethod(rootObj, "callMeBack",
Q_ARG(QVariant, QVariant::fromValue(callBackData)));
Not tested, but should work (QML recognize QObject* type).