Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

juic & signal slot in QtJambi 5.15.2 #17

Closed
ITSweets opened this issue Dec 9, 2021 · 36 comments
Closed

juic & signal slot in QtJambi 5.15.2 #17

ITSweets opened this issue Dec 9, 2021 · 36 comments

Comments

@ITSweets
Copy link

ITSweets commented Dec 9, 2021

Hi. I want to learn this framework. But in the java binding in Qt5.15.2, I did not see the tool (jui.exe) to convert .ui files. How should it be converted? In addition, there are links between signals and slots. Are there any sample programs? All on the Internet are version 4.x. Looking forward to your reply.

@omix
Copy link
Contributor

omix commented Dec 9, 2021

Hi. There is no longer a jui tool. I dropped this from the project because it was not maintained for over 10 years and I was not able to get this code running with Qt5 in a reasonable amount of time. Additionally, jui needed a patched version of designer because it did not work with ui files but with jui files specific for Java. Both tools were way too outdated to bring them to newest Qt versions. Maybe in the future, I will develop it again from scratch but unclear. I assumed working with UI files has been gradually replaced by QML.

UI files can be loaded by QUiLoader at runtime. Take a look at test file TestUiTools.java to look how it works.

@omix omix closed this as completed Dec 9, 2021
@omix omix reopened this Dec 9, 2021
@ITSweets
Copy link
Author

ITSweets commented Dec 9, 2021

All right. I did not see TestUiTools.java. Can you tell me where he is? In addition, there is no QUiLoader class. There is. I used qml and java. But when looking up an object in qml, it always returns null. Why?

The content of App.java is as follows:

public class App
{
    public static void main(String[] args)
    {
        QGuiApplication.initialize(args);
        QtUtilities.initializePackage("io.qt.quick");

        QQmlApplicationEngine engine = new QQmlApplicationEngine();
        engine.load(QUrl.fromClassPath("main.qml"));


        QLabel label02 = engine.rootObjects().first().findChild(QLabel.class, "label02");

        System.out.println(label02); // return is null why?

        QGuiApplication.exec();
        QGuiApplication.shutdown();

    }
}

The content of main.qml is as follows

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window
{
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")


    Label
    {
        objectName: "label02"
        text: "QML AND CPP 02"
        font.pixelSize: 22
        color: "#00C20C"
        y: 20

        function getText(name)
        {
            return text + name
        }
    }

}

@omix
Copy link
Contributor

omix commented Dec 9, 2021

Oh, I see, QUiLoader is Qt6. In Qt5 you need to use QFormBuilder from module qtjambi.designer.
Unit test file io.qt.autotests.TestDesigner.java shows how to use it. You can find test files in src/java/autotest.

@omix
Copy link
Contributor

omix commented Dec 9, 2021

Regarding your qml example:

I never used QQmlApplicationEngine this way and I am also not experienced in QML.
However, you could make sure if QUrl.fromClassPath("main.qml") resolves the path to the qml file correctly.
You could print out ths url path and if it is valid.
On the other side, I think your code at least produces a non-empty rootObjects list. Only there is no QLabel. Could you check the object tree produced by QML?

Following tests deal with QML, maybe you could learn from it:

  • io/qt/autotests/TestQml.java
  • io/qt/autotests/TestQml2.java
  • io/qt/autotests/TestQuickQt5.java
  • io/qt/autotests/TestQuickWidgetsQt5.java
  • io/qt/autotests/TestQml.java
  • io/qt/autotests/TestQml.java

@omix
Copy link
Contributor

omix commented Dec 9, 2021

Oh, no, now I see the mistake: QML type Label is not widget type QLabel. You don't produce widgets in QML. These components are realized as QQuickItem.

QQuickItem label02 = engine.rootObjects().first().findChild(QQuickItem.class, "label02");

...should fix it.

@ITSweets
Copy link
Author

ITSweets commented Dec 9, 2021

I found the problem.
Using QUrl.fromClassPath("main.qml") and new QUrl("classpath:main.qml") to load qml can be displayed normally. If you use the former, you can get the label02 object just now (after you change it to the code you provided and fix it), but if you use the latter, there will be an exception Accessing container of size 0 at 0. Obviously, the object tree is empty. I don't understand why this happens.

In another project, the traditional widget approach
window.ui is under the classpath, I just used QFormBuilder and QFile file = new QFile("classpath:window.ui"); both work normally
Why can't I get the main.qml (QUrl("classpath:main.qml")) under the classpath in the same way?

In addition, QMetaObject.invokeMethod(label02, "getText", "ITSweets") throws an exception "main" io.qt.QNoSuchMethodException: Method not found: getText

The method of calling qml components here is also different from c++?

I prepared for a while and I was almost done, recording a set of tutorials. Because the tutorials on this framework in China are still Qt4 around 2014. I don't want such an excellent framework to be buried. After all, it is based on the powerful Qt

@omix
Copy link
Contributor

omix commented Dec 9, 2021

new QUrl("classpath:main.qml") does not work, because it is invalid. You can use files with classpath: prefix:
new QFileInfo("classpath:main.qml"), thus you can create an url from file this way: QUrl.fromLocalFile("classpath:main.qml").
But if you want to use the url constructor you need to specify a file: url: new QUrl("file:/:classpath:main.qml")

classpath: is not an url schema like http, ftp, mailto and so on. Thus, an url starting with classpath: is not recognized. Instead, classpaths are local file paths. Thus you need to use the file: prefix as url schema when using classpath urls.

@ITSweets
Copy link
Author

ITSweets commented Dec 9, 2021

QUrl("file:/:classpath:main.qml") cannot get the object in this way, but the form can be displayed normally. very strange. correct
QObject.connect(lineEdit.textChanged, EventHandler::fun02);
EventHandler::fun02 is correct to write like this. But changing to EventHandler.fun02(); will throw an exception. Why is it not working? They are all called statically.

@omix
Copy link
Contributor

omix commented Dec 9, 2021

QMetaObject.invokeMethod(label02, "getText", "ITSweets")

Maybe you should make yourself familiar with available methods in label02:

label02.mataObject().methods().forEach(m->System.out.println(m.cppMethodSignature()));
Is there a method "getText"?
If you know it's method signature, you could also use it: "getText(QString)"

@omix
Copy link
Contributor

omix commented Dec 9, 2021

QUrl("file:/:classpath:main.qml")

Ok, maybe its rather QUrl("file:///:classpath:main.qml")

@omix
Copy link
Contributor

omix commented Dec 9, 2021

But changing to EventHandler.fun02(); will throw an exception.

I don't understand what you are doing causing the exception. Can you give me more code?

@ITSweets
Copy link
Author

ITSweets commented Dec 9, 2021

This is my code

         QObject.connect(button_cancel.clicked, APPWidget.callback());
         QObject.connect(button_cancel.clicked, APPWidget::fun02);

A processing function is added for button clicks, in the form of APPWidget.callback()

The corresponding function implementations are

     public static QMetaObject.Slot0 callback()
     {
         return new QMetaObject.Slot0()
         {
             @Override
             public void invoke() throws Throwable
             {
                 System.out.println("callback");
             }
         };
     }

The above is very troublesome, is there no implementation class of QMetaObject.Slot0 interface? Even lambda is just a little bit simpler.

@ITSweets
Copy link
Author

ITSweets commented Dec 9, 2021

After printing the method name of the object in qml to the console, it is found that there is indeed this method.

getText(QVariant)

Using Object getText1 = QMetaObject.invokeMethod(label02, "getText(QVariant)", "ITSweets"); Executing this method will throw the following exception. Obviously there are, so strange. Is the parameter passing way wrong?

rightInsetChanged()
bottomInsetChanged()
getText(QVariant)

Exception in thread "main" io.qt.QNoSuchMethodException: Method not found: getText(QVariant)
at io.qt.core.QMetaObject.invokeMethod(QMetaObject.java:862)
at io.qt.core.QMetaObject.invokeMethod(QMetaObject.java:814)
at vip.itsweets.AppQml.main(AppQml.java:45)

If the method in qml is getText() without parameters, use Object getText = QMetaObject.invokeMethod(label02, "getText()"); The call is normal, and the expected result can be returned

@omix
Copy link
Contributor

omix commented Dec 9, 2021

The only reason for QMetaObject.SlotX interfaces is to allow lambda expressions and method handles to be connected to signals.

So, make a connection like this:

QObject.connect(button_cancel.clicked, APPWidget::fun02);
QObject.connect(button_cancel.clicked, clicked -> { APPWidget.fun02(); });

However, custom implementations of QMetaObject.SlotX (or anonymous classes as you did) should be supported as well without error.

@omix
Copy link
Contributor

omix commented Dec 9, 2021

Using Object getText1 = QMetaObject.invokeMethod(label02, "getText(QVariant)", "ITSweets"); Executing this method will throw the following exception.

what happens if you use Java method signature: getText(Object)? Qt's QVariant maps to Java's java.lang.Object.

You could also do this in two steps:

object.metaObject.method("getText", Object.class).invoke(...);

@ITSweets
Copy link
Author

ITSweets commented Dec 9, 2021

The dynamic loading of the .ui file just mentioned, then if you want to rewrite some events of the dynamically loaded form (such as closeEvent, eventFilter, etc.) how to achieve it. There is no idea.

@omix
Copy link
Contributor

omix commented Dec 9, 2021

The dynamic loading of the .ui file just mentioned, then if you want to rewrite some events of the dynamically loaded form (such as closeEvent, eventFilter, etc.) how to achieve it. There is no idea.

I believe there is no way to override methods from your UI-defined objects because there is no class generation. You could define custom subclasses of certain widget types with your intended overridden methods and then use these types in UI form.

@ITSweets
Copy link
Author

ITSweets commented Dec 9, 2021

The result of using getText(Object) and getText(QObject) is the same as getText(QVariant). Is this not feasible?

@ITSweets
Copy link
Author

ITSweets commented Dec 9, 2021

I have seen many .jui files in your source code. Did you also use the juic.exe of Qt 4.x to generate it? I think you should develop juic. He is very important. Or what method did you use to generate .jui?

@omix
Copy link
Contributor

omix commented Dec 9, 2021

The result of using getText(Object) and getText(QObject) is the same as getText(QVariant). Is this not feasible?

QObject is definitely wrong because QObject is not QVariant. Object (without Q) is the top level Java class type. However, I will check out if there is a bug in QtJambi.

@omix
Copy link
Contributor

omix commented Dec 9, 2021

I have seen many .jui files in your source code. Did you also use the juic.exe of Qt 4.x to generate it? I think you should develop juic. He is very important. Or what method did you use to generate .jui?

Before 2009, QtJambi project was developed by the makers of Qt. There is a lot of old code in the project I don't care about. Especially the examples are totally outdated for over a decade. They don't work at all. Maybe, I should drop them from the project.

@ITSweets
Copy link
Author

ITSweets commented Dec 9, 2021

So will you always maintain this project?

@omix
Copy link
Contributor

omix commented Dec 9, 2021

I do since 2009 and I will continue.

@omix
Copy link
Contributor

omix commented Dec 9, 2021

The result of using getText(Object) and getText(QObject) is the same as getText(QVariant). Is this not feasible?

I checked your code example and I don't get any errors here:

QByteArray data = new QByteArray("import QtQuick 2.15\n" + 
		"Item {\n" + 
		"    function test(s){console.info(s)}\n" + 
		"}");
QQmlApplicationEngine engine = new QQmlApplicationEngine();
engine.loadData(data);
QObject object = engine.rootObjects().first();
QMetaObject.invokeMethod(object, "test(java.lang.Object)", "TEST1");
QMetaObject.invokeMethod(object, "test(Object)", "TEST2");
QMetaObject.invokeMethod(object, "test(QVariant)", "TEST3");

Resulting in outprint:

INFORMATION: TEST1
INFORMATION: TEST2
INFORMATION: TEST3

@ITSweets
Copy link
Author

ITSweets commented Dec 9, 2021

In my case, only QMetaObject.invokeMethod(object, "test(java.lang.Object)", "TEST1"); is executed as expected, and the other two throw exceptions as before, saying that the method cannot be found. It doesn't work even if the parameter is Object, it must be the full class name (java.lang.Object) to be normal. Damn it. . .

@ITSweets
Copy link
Author

ITSweets commented Dec 9, 2021

In order to further your understanding, I recorded a few seconds of video.
Link: https://pan.baidu.com/s/1htwRbBZLXlHlktgnx9XXwQ
Extraction code: 0000

@omix
Copy link
Contributor

omix commented Dec 9, 2021

It was not easy to see the whole video because it buffers all the time. I think I got it. Can you debug through these lines step by step? Is it Application.exec() never returning but resulting in a crash? Is it actually a crash? Java crashes look different from that. What Java are you using?

@ITSweets
Copy link
Author

ITSweets commented Dec 9, 2021

I am using QGuiApplication.exec();
Development environment: windows 11 64Bit idea2021.2.3
jdk1.8.0_311

@omix
Copy link
Contributor

omix commented Dec 9, 2021

I watched it again. Actually, it did not crash. You terminated the program in your IDE. QGuiApplication.exec(); is here an endless loop that will never end because you don't have any visible UI.

So, what's your question with the video? Maybe, I did not understand.

@ITSweets
Copy link
Author

ITSweets commented Dec 9, 2021

That is, QMetaObject.invokeMethod(object, "test(java.lang.Object)", "TEST1"); in the video can be executed as expected. The other two are throwing exceptions. Did not get the expected return value (I changed your console.info(s) to return s)

@omix
Copy link
Contributor

omix commented Dec 9, 2021

That is, QMetaObject.invokeMethod(object, "test(java.lang.Object)", "TEST1"); in the video can be executed as expected. The other two are throwing exceptions. Did not get the expected return value (I changed your console.info(s) to return s)

Ah, ok. I see. So, I am using a revised version of QtJambi which I will publish shortly. There, it is possible to use it in different ways:

  • QMetaObject.invokeMethod(object, "test(java.lang.Object)", "TEST1");
  • QMetaObject.invokeMethod(object, "test(Object)", "TEST1");
  • QMetaObject.invokeMethod(object, "test(QVariant)", "TEST1");
  • QMetaObject.invokeMethod(object, "test", "TEST1");

All suitable to call a QML defined function "test" with variant argument.

@ITSweets
Copy link
Author

Hi, I'm here again. Is the patch version of Qt 5.15.2 updated? It's about the parameters of the signal slot. Does this problem also exist in Qt 6? Qt seems to have been updated to Qt 6.2.2.

@omix
Copy link
Contributor

omix commented Dec 16, 2021

Hi. I don't understand your question.

As far as I see, version 5.15.2 and 6.2.2 are the latest available open source releases of Qt.

Qtjambi got a patch update. Here, the versions are 5.15.3 and 6.2.1 (first and second number refer to Qt and the third number is the QtJambi release).

In QtJambi 5.15.3 and 6.2.1 the solution mentioned above is realized.

@ITSweets
Copy link
Author

Okay, blame me for not expressing clearly. This is your last reply. This is the problem I just mentioned.

Ah, ok. I see. So, I am using a revised version of QtJambi which I will publish shortly. There, it is possible to use it in different ways:

QMetaObject.invokeMethod(object, "test(java.lang.Object)", "TEST1");
QMetaObject.invokeMethod(object, "test(Object)", "TEST1");
QMetaObject.invokeMethod(object, "test(QVariant)", "TEST1");
QMetaObject.invokeMethod(object, "test", "TEST1");
All suitable to call a QML defined function "test" with variant argument.

I saw that you released a new version two days ago, but I did not find the download link of qtjambi 5.15.3 on the re-release page. Is it a link to qtjambi 5.15.2?

@omix
Copy link
Contributor

omix commented Dec 16, 2021

You should follow the instructions under QtJambi modules.
Binaries are now published in Maven.

@omix
Copy link
Contributor

omix commented Apr 4, 2022

UIC tool now available as module qtjambi.uic.

@omix omix closed this as completed Apr 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants