Opinions regarding Qt for Python for cross-platform desktop apps

Hello everyone,

I am in the process to start a new project, a cross platform desktop app to handle documents (PDFs) and attached metadata and I am investigating various frameworks and languages that better suits my requirements. Other than that, I would like to use a programming language that has many libraries, good tooling with a great developer experience and a good community. I already have experience with JavaScript, Swift and Python but it would not a big problem to pick a new language.

Xojo was one of my first choice (it’s how I landed here) but reading the posts in this forum I am not sure it would be a great choice for the direction it is going to take.
I excluded JS based frameworks (Electron, Tauri, etc) as I would like to have a native (or native-like) user experience (and I don’t like the big package size and memory comsumption even if in 2023 this is not a big issue)
Flutter for desktop is quite stable nowadays but the GUI that allows to create is not very well integrated with the rest of other apps and I don’t feel it has a great support for DB/business oriented apps.

So I had 4 choices in my short list: Lazarus (LCL), XULRunner (Mozilla Platform), Qt (Python bindings) and JavaFX:

  • Lazarus on the paper has great feature: native widgetset (it uses Cocoa/AppKit on macOS for example), has a RAD IDE with drag&drop, database support, small and fast binary (even for Apple Silicon), but it uses a very verbose language (Object Pascal) and the community is quite small. And I don’t find many apps open source and material to learn from;
  • XULRunner, the technology behind Firefox, Thunderbird can be used to create standalone apps (my preferred one is Zotero (https://www.zotero.org) and apps made with it seems very snappy and native. However Mozilla abandoned the platform 8 years ago
  • Qt seems quite popular and I use many apps everyday written with Qt that are fast and feel native. So it feels solid and stable. The problem here is that I don’t have experience with C++ (and I am a bit reluctant to learn it at the moment) so I would like to use some Python bindings (like Pyside6 or PyQT). But I am not sure how well an app will performe (in term of GUI responsivness I mean)
  • finally I read a lot of topic in this forum about JavaFX and some here have chosen that technology as Xojo replacement

So I would love to have your opinions as I feel many have experienced that same investigation process, in particular regarding Qt with Python and JavaFX, or any other suggestion in case I have missed some other alternatives.

thank you in advance

Try JavaFX, I would say no problem to write that App with it.

1 Like

I was not choosing it as a Xojo replacement, a customer of mine wanted to use Xojo for one project. All other projects I wrote I was using Java / JavaFX. For me there would not be any alternative using it while there is no cross platform alternative. QT C++ is the hell for cross platform while on every platform you have to do many steps for gettig it compiled and often also changes. I would prerfere FX.

I will surely give it a try, especially since recently it has been updated. I would be interested if an app is easy to package for different OS and how big is the package size. I would like also to know if packaged JavaFX apps can be distributed via mac App Store (I guess not, as Apple doesn’t allow JIT compilation)
Do you have suggestions for any JavaFX apps that can I download and test it out to experiment and have a feeling of the UI responsivness? thanks

My plan would be to avoid C++ and use QT via Python (Qt for Python). I am wondering if anyone else in this forum gave it a try

I am using Github Actions to build for all platforms but it is also possible to use local computers. No problem.

You can use IntelliJ CE for free and use a Maven archetype. When I am home I will look if I find a description for it.

Packaging Python for distribution might be as big a challenge as anything

1 Like

Packaging QT Python not only Python. Python is simple. QT Python not.

it seems that recently a deploy tool has been released that makes the operation very easy:

https://doc.qt.io/qtforpython-6/deployment/deployment-pyside6-deploy.html

I have tried to package a simple hello_world and it produced a binary file of 19Mb, everything included.

1 Like

The challenge is that it is not about size of deployment, or Mac or Windows Store etc. it is about you, it is about your work, it’s about your resilience to learn something new and to be stubborn enough to stay with it. So my suggestion would be to go for something which exist for a long time and is well documented.

The good thing with Java is that you can find literally libraries for everything. And surprisingly JavaFX is quite impressive. You either compile for all major platforms via Github actions and similar CIs or you compile it per OS. But I have one script doing it for all of them, so it is basically nothing more than pulling the git and compiling it on a couple of machines / VMs. And the good thing is that he really looks the same on each OS. But that again depends on you, if you want to get the 100 percent “native” look, use C# and Swift.

Ask yourself the question: how many people will be able to help you with QT Python, versus Swift, C#, Java(Fx) … :wink:

Thank you for the opinion.
I am trying to give a try first to app developed with a given framework. For example I discovered XULRunner using Thunderbird and Zotero and I liked a lot and felt very stable.
I would like to do the same with JavaFX. I have already downloaded JavaFX ensemble 8 and JFX Central. Neither of that impressed me.
Could you please point me to where I can download and try some good apps made with JavaFX?

This guy is building macOS “almost native” apps Devnexus 2022 - May JavaFX be with you always - Gerrit Grunwald - YouTube, you can design literally anything you want.

And it comes up with impressive Speed. You only need to learn Java and JavaFX. And there it starts:

use Maven archetypes for it (so learn about maven to get more information) and get Java, Maven installs the needed JavaFX runtimes. Get Scenebuilder, get the fxml archetype. Learn. Jeannot knows a lot of good tutorials, possibly he would be so friendly to recommend a few.

Apps with JavaFX? Too many to write them all here. Many big ones running JavaFX, banking apps, business apps…

1 Like

I have performed work with wxWidgets and Qt before with cross-platform C++ for the desktop. Both wxWidgets and Qt are very stable, and Qt is easier than wxWidgets. Qt is a little expensive, and programming can be challenging since there are many options for each control.

The issue with Python is that the language is interpreted. Compilers exist for Python and I have run into issues where the compiler can’t understand the code - LOL, it could have been my way of thinking that was causing issues with the compiler. Sometimes the compilers work well on one OS (Windows) and may fail on another (Mac). Compiled programs are faster and the difference in speed between compiled programs and interpreted (Python, Java) language execution is shrinking.

If you need bleeding-edge speed, then I would suggest C++ with Qt or wxWidgets. If the program is to have moderate performance and good cross-platform compatibility, then Python with Qt or wxWidgets would be perfectly fine. Qt and wxWidgets do have quite a learning curve. Qt has a long-list of legal things that you shouldnt (can’t?) do, such as make your own language, and the documentation is quite good. wxWidgets is free and you are able to do almost anything, but (there is always a but) the documentation is poorer.

thank you @Jeannot thorstenstueker for the advices on Java/JavaFX. I have installed the SDK, written a simple Hello World GUI app and run it. I have noticed a quite usage of RAM (400Mb) for this simple app. However SceneBuilder is very cool. I was trying to package the app into a DMG with jpackage to see how big it will be the installation bundle, but I wasn’t. I need to investigate more on how to create before the .JAR file containing the HelloWorld.class.

However at the moment my preference is going to Qt with Python. Why? I already know Python quite well and productive with it for writing the business logic of my app. I have basic knowledge of Java and I don’t use it since the days of the University (I have a degree and PhD in CS) and used only for academic staff. So my idea is to go with Qt at the moment and if I will fail and I will go with the JavaFX route.

@eugenedakin thank you for the comments. I am aware of the controversial licensing model of Qt. The idea would be to start with the LGPL license and if it would be satisfactory I could buy a license ($499 for small company). Surely using C++ with Qt would be much better, but this requires to learn C++ (and it would be much harder then refreshing my Java knowledge). I am not concerned to much with speed: my goal here is to build GUI apps, that don’t require too much performance, users need to interact with a GUI and the backend has logic to store data in files and db, and call network apis. Those are not area, for my use cases at least, that would be a problem in that regard.

Packaging instead is one of my concern. I have tried pyside6-deploy that uses the Nuitika compiler under the hood (compile Python code in C) and, even if it produced a single binary of 19mb (on macOS at least) with all the deps included, it required 5 sec to load a Qt-for-python HelloWorld (if I use the pyc code, it was almost instantaneus). Probably I need to use some optimazion flag, but I haven’t further investigate at the moment.

The other concern with both JavaFX and Qt for Python is deploying to app store, especially to the mac app store. I have to investigate more to find out if any of the two techs would be able to produce binary that would be accepted in that store. This is another quite important requirement for my use case.

Finally, being a native mobile developer too, I know Swift quite well. Unfortunately Swift GUI frameworks target only macOS at the moment, but in the latest WWDC Apple announced C++ interoperability with Swift 5.9. This means that, in principle, one can call any C++ APIs and libraries directly from Swift, without any binding or bridge. I am investigating this route (maybe I will open a new post in case someone is interested) that would allow to build native cross-platform apps (and compiled) with any C++ GUI framework (like WxWidgets or Qt)

Do you believe this is feasible?

The Memory consumption is cause of the Java JRE which is loaded for runtime. For a command line app I would always compile with native Image, for a GUI application as a Web App: if it would be for web we would speak about a Server and then it is small size also when speaking about mobile it is nativer compiled and also in a much lower size.

The speed up between compiled and JDK run is I would say 10x faster, the behavior with big data amounts JRE running is not beatable with any compiled one. Not python not C++ comes in the same area.

how can I compile a JavaFX app wirh native image? Can I deploy it to App Store?

ok I see that GraalVM or Liberica Native Image Kit could do that. I will test it out

Liberica Native Image Kit doesn’t include the HTML Viewer yet. And GraalVM has limitations too when talking about JavaFX, depending on what kind of libraries you are using under the hood. But again it is quite simple to generate with gradle packages for Windows, macOS, Linux. This one gives you an example fo one of my apps generating automatically a binary after running the “jpackage” task on a windows, macos or linux machine. Nothing to be changed on the script, it just needs to be run on the respective systems. You will get a pkg on macOS and an windows installer on Windows etc.

It even generates the online help with the build (vitepress) and uploads it. And it signs the macOS files with my certificates.

import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.internal.os.OperatingSystem

buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
}

// Loading all necessary plugins
plugins {
    id("java-library")

    id("com.dipien.releaseshub.gradle.plugin") version "4.0.0"

    // https://github.com/java9-modularity/gradle-modules-plugin
    id("org.javamodularity.moduleplugin") version "1.8.12"

    // https://github.com/openjfx/javafx-gradle-plugin
    id("org.openjfx.javafxplugin") version "0.0.14"

    // https://github.com/beryx/badass-jlink-plugin/
    id("org.beryx.jlink") version "2.26.0"

    // https://github.com/int128/gradle-ssh-plugin
    id("org.hidetake.ssh") version "2.11.2"
}

repositories {
    mavenLocal()
    mavenCentral()
}

apply(plugin: "java")

// Loading all necessary properties from gradle.properties file
// Java Version to use
targetCompatibility = appJavaVersion.toString()
sourceCompatibility = appJavaVersion.toString()

var currentOS = OperatingSystem.current()

// Project meta data
project.description = appDescription
project.ext.buildDate = new Date()
project.version = appVersion

run {
    jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005'
}

// Future dependencies to be added here
dependencies {
    implementation "org.apache.commons:commons-lang3:3.12.0"
    implementation "org.apache.poi:poi:5.2.3"
    implementation "org.apache.poi:poi-ooxml:5.2.3"
    implementation "org.apache.logging.log4j:log4j-core:2.20.0"
    implementation "com.univocity:univocity-parsers:2.9.1"
    implementation "org.controlsfx:controlsfx:11.1.2"
    implementation "org.hsqldb:hsqldb:2.7.2"


}

if (Os.isFamily(Os.FAMILY_MAC)) {
    remotes {
        webServer {
            host = "XXXXXXXXXXXX.de"
            user = "XXXXXXXXXXXX"
            identity = file("/Users/jmu/.ssh/id_rsa")
        }
    }
} else {
    remotes {
        webServer {
            host = "XXXXXXXXXXXX.de"
            user = "XXXXXXXXXXXX"
            identity = file("C:\\Users\\jmu\\.ssh\\id_rsa")
        }
    }
}

tasks.register("deployOnlineHelp") {
    doLast {
        ssh.run {
            session(remotes.webServer) {
                remove "/var/www/html/csv-converter.teccompanion.com/dist"
                put from: "${projectDir}/vitepress/.vitepress/dist/", into: "/var/www/html/csv-converter.teccompanion.com"
            }
        }
    }
}

// JavaFX dependencies
javafx {
    version = appJavaFxVersion.toString()
    // Read all Java FX modules from the properties file
    def mods = []
    for (mod in appJavaFxModules.split(",")) {
        mods.add(mod)
    }
    modules = mods
}

// Setting the environment
application {
    mainModule = appMainModule
    mainClass = appMainClass
}

// define a pre-build  task to build the vitePress docs beforehand
tasks.register('vitePress', Exec) {
    workingDir "${projectDir}/vitepress" // replace with your actual app path
    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        commandLine "npm.cmd", "run", "docs:build"
    } else {
        commandLine "npm", "run", "docs:build"
    }
}

// make this task a dependency of the build and the run task
deployOnlineHelp.dependsOn(vitePress)
jpackage.dependsOn(deployOnlineHelp)

// JLink for modular projects
jlink {

    mergedModule {
        requires "java.xml"
    }
    // Some default options
    options.set(["--strip-debug", "--compress", "2", "--no-header-files", "--no-man-pages"])
    launcher {
        name = rootProject.name
            jvmArgs = ["-p", ".", "-Djdk.gtk.version=2", "-DFile.encoding=UTF-8"]
    }

    // Pack it!
    jpackage {
        //Resolve the used operating system
        targetPlatformName = ""
        if (currentOS.macOsX) {
            targetPlatformName = "mac"
        } else if (currentOS.linux) {
            targetPlatformName = "linux"
        } else if (currentOS.windows) {
            targetPlatformName = "win"
        }

        // Resource directory for native package overrides,
        resourceDir = file(appResources)

        java {
            toolchain {
                languageVersion = JavaLanguageVersion.of(appJavaVersion)
                modularity.inferModulePath.set(true)
            }
        }

        if (targetPlatformName == "mac") { // we are on mac
            installerType = "pkg" // we want to have macOS PKG
        }
        if (targetPlatformName == "win") { // we are on Windows - msi for msi installer
            installerType = "exe"
        }
        if (targetPlatformName == "linux") { // we are on linux
            installerType = "deb"
        }

        // Add jpackage-specific options
        installerOptions = ["--name", rootProject.name, // installer name
                            "--description", project.description,
                            "--copyright", appCopyright,
                            "--vendor", appVendor,]

        // Add platform-specific options for the target image and for jpackage
        if (installerType == "pkg") {
            imageOptions += ["--mac-sign", "--icon", appResources + "icon.icns",
                             "--mac-package-name", "CSV Converter",
                             "--mac-package-signing-prefix", "com.teccompanion.csvconverter.common",
                             "--mac-signing-key-user-name", "Jeannot Muller (XXXXXXXXXXXX)",
                             "--mac-signing-keychain", "/Users/jmu/Library/Keychains/login.keychain-db"]

            installerOptions += ["--license-file", appResources + "LICENSE-OS-Installer.txt",
                                 "--mac-package-identifier", "com.teccompanion.csvconverter.common",
                                 "--mac-sign",
                                 "--mac-package-name", "CSV Converter",
                                 "--mac-package-signing-prefix", "com.teccompanion.csvconverter.common",
                                 "--mac-signing-key-user-name", "Jeannot Muller (XXXXXXXXXXXX)",
                                 "--mac-signing-keychain", "/Users/jmu/Library/Keychains/login.keychain-db"]
        }
        if (installerType == "exe") {
            icon = appResources + "icon.ico"
            imageOptions += ["--icon", appResources + "icon.ico"]
            installerOptions += ["--resource-dir", appResources]
            installerOptions += ["--win-per-user-install", // Install only for current user
                                 // "--win-console", // Shows what Java outputs to the console
                                 //"--win-dir-chooser",
                                 "--win-menu",
                                 "--win-shortcut",]
        }
        if (installerType in ["deb", "rpm"]) {
            imageOptions += ["--icon", appResources + "icon_256x256.png"]
            installerOptions += ["--linux-menu-group",
                                 "Tools",
                                 "--linux-shortcut"]
        }
        if (installerType == "deb") {
            installerOptions += ["--linux-deb-maintainer", appEmailAdress]
        }
        if (installerType == "rpm") {
            installerOptions += ["--linux-rpm-license-type",
                                 "GPLv3"]
        }
    }
}

This is the vitepress generated online help