pythonwindowsdllpyqt6

PyQt6 on Windows : qtquickcontrols2windowsstyleimplplugin.dll: The specified module could not be found


i am trying to test some code using PyQt6. When I try to display en MenuBar in my main.qml, i have this error :

QQmlApplicationEngine failed to load component
file:///C:/Users/[blablabla]/GUIt/main.qml:11:9: Type Menu unavailable
qrc:/qt-project.org/imports/QtQuick/Controls/Windows/Menu.qml:32:15: Type MenuItem unavailable
qrc:/qt-project.org/imports/QtQuick/Controls/Windows/MenuItem.qml:7:1: Impossible de charger la bibliothÞque C:\Users\[blablabla]\Python\Python313\Lib\site-packages\PyQt6\Qt6\qml\QtQuick\Controls\Windows\impl\qtquickcontrols2windowsstyleimplplugin.dllá: Le module spÚcifiÚ est introuvable.

(Sorry i'm french so my errors too, but "Le module spÚcifiÚ est introuvable." means "The specified module could not be found")

I could not find any solved issue similar to my problem so i try my luck here.

I am on Windows 11, and I am using Python3.13.3 (not the native Windows installation)

Here is my code : main.py :

from PyQt6.QtWidgets import QApplication
from PyQt6.QtQml import QQmlApplicationEngine
from backend import Backend

app = QApplication([])
engine = QQmlApplicationEngine()

backend = Backend()
engine.rootContext().setContextProperty("pybackend", backend)

engine.load("main.qml")

if not engine.rootObjects():
    import sys
    sys.exit(-1)

app.exec()

backend.py :

from PyQt6.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
from PyQt6.QtWidgets import QFileDialog
from git import Repo
from models.CommitList import CommitListModel

class Backend(QObject):
    repoPathChanged = pyqtSignal()
    repoNameChanged = pyqtSignal()
    commitListChanged = pyqtSignal()

    def __init__(self):
        super().__init__()
        self._commit_list = CommitListModel()
        self._repo_path = ""
        self.repo = None
        self._repo_name = ""

    @pyqtProperty(str, notify=repoPathChanged)
    def repoPath(self):
        return self._repo_path

    @pyqtProperty(str, notify=repoNameChanged)
    def repoName(self):
        return self._repo_name

    @pyqtProperty(QObject, notify=commitListChanged)
    def commitList(self):
        return self._commit_list

    @pyqtSlot()
    def chooseAndLoadRepo(self):
        dialog = QFileDialog()
        dialog.setFileMode(QFileDialog.FileMode.Directory)
        dialog.setOption(QFileDialog.Option.ShowDirsOnly, True)
        if dialog.exec():
            selected_dirs = dialog.selectedFiles()
            if selected_dirs:
                self.loadRepo(selected_dirs[0])  # charge le dossier sélectionné

    @pyqtSlot()
    def loadRepo(self, path = "C:/Users/lucie/Hesias/B2/MajorProject/major-project-b2"):
        try:
            self.repo = Repo(path)
            self._repo_path = path
            self._repo_name = path.split("/")[-1]
            self.repoPathChanged.emit()
            self.repoNameChanged.emit()
        except Exception as e:
            print(f"Erreur : {e}")

    @pyqtSlot()
    def loadCommits(self):
        try:
            commits = [f"{c.hexsha[:7]}: {c.summary}" for c in self.repo.iter_commits()]
            print(commits)
            self._commit_list.setCommits(commits)
            self.commitListChanged.emit()
        except Exception as e:
            self._commit_list.setCommits([f"Erreur : {e}"])

models/CommitList.py :

from PyQt6.QtCore import QAbstractListModel, Qt, QModelIndex

class CommitListModel(QAbstractListModel):
    def __init__(self, commits=None):
        super().__init__()
        self._commits = commits or []

    def data(self, index, role):
        if role == Qt.ItemDataRole.DisplayRole and index.isValid():
            return self._commits[index.row()]
        return None

    def rowCount(self, index):
        return len(self._commits)

    def setCommits(self, commits):
        self.beginResetModel()
        self._commits = commits
        self.endResetModel()

main.qml :

import QtQuick
import QtQuick.Controls

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("GUIt")

    MenuBar {
        Menu {
            title: "Fichier"

            Action {
                text: "Charger un dépôt"
                onTriggered: pybackend.chooseAndLoadRepo()
            }

            // Tu peux ajouter plus d'actions ici
            // Action {
            //     text: "Autre option"
            //     onTriggered: { /* action ici */ }
            // }
        }

        // Ajoute un menu "Édition"
        Menu {
            title: "Édition"

            Action {
                text: "Option 1"
                onTriggered: { /* action ici */ }
            }

            Action {
                text: "Option 2"
                onTriggered: { /* action ici */ }
            }
        }
    }


    Column {
        anchors.centerIn: parent
        spacing: 10

        Text {
            text: pybackend.repoPath !== "" ? "Repo actuel : " + pybackend.repoName : "Aucun dépôt chargé"
            font.pixelSize: 20
        }

        Button {
            text: "Charger le dépôt"
            // onClicked: pybackend.loadRepo()
            onClicked: pybackend.chooseAndLoadRepo()
        }

        Button {
            text: "Charger les commits"
            onClicked: pybackend.loadCommits()
            enabled: pybackend.repoPath !== ""
        }

        ListView {
            width: parent.width
            height: 300
            model: pybackend.commitList
            delegate: Text {
                text: model.display  // ou juste `modelData` si le rôle par défaut est utilisé
                font.pixelSize: 14
                padding: 4
            }
        }
    }
}

Solution

  • The last version of QtQuick.Controls which supports a natively rendered Windows MenuBar was version 1.4. Unfortunately, for your current code, QtQuick.Controls available with PyQt6 is version 2. In order to use QtQuick.Controls 1.4, you have to use PyQt5.

    Use PyQt5 instead of PyQt6 in main.py, backend.py, and CommitList.py.

    Then, in your main.qml file, specify the QtQuick and QtQuick.Controls versions as show in the code below. In the second menu item, I changed "Action" to "MenuItem", because I'm guessing that is what you really intended.

    The menu bar should be specified as:

    menuBar: MenuBar {
    

    Also, "padding: 4" is not supported with the above changes.

    I've marked all of the lines that I changed in main.qml with "// CHANGED".

    import QtQuick 2.5            // CHANGED
    import QtQuick.Controls 1.4   // CHANGED
    
    ApplicationWindow {
        visible: true
        width: 640
        height: 480
        title: qsTr("GUIt")
    
        menuBar: MenuBar {         // CHANGED
            Menu {
                title: "Fichier"
    
                Action {
                    text: "Charger un dépôt"
                    onTriggered: pybackend.chooseAndLoadRepo()
                }
    
                // Tu peux ajouter plus d actions ici
                // Action {
                //     text: "Autre option"
                //     onTriggered: { /* action ici */ }
                // }
            }
    
            // Ajoute un menu "Édition"
            Menu {
                title: "Édition"
    
                MenuItem {              // CHANGED
                    text: "Option 1"
                    onTriggered: { /* action ici */ }
                }
    
                MenuItem {              // CHANGED
                    text: "Option 2"
                    onTriggered: { /* action ici */ }
                }
            }
        }
    
    
        Column {
            anchors.centerIn: parent
            spacing: 10
    
            Text {
                text: pybackend.repoPath !== "" ? "Repo actuel : " + pybackend.repoName : "Aucun dépôt chargé"
                font.pixelSize: 20
            }
    
            Button {
                text: "Charger le dépôt"
                // onClicked: pybackend.loadRepo()
                onClicked: pybackend.chooseAndLoadRepo()
            }
    
            Button {
                text: "Charger les commits"
                onClicked: pybackend.loadCommits()
                enabled: pybackend.repoPath !== ""
            }
    
            ListView {
                width: parent.width
                height: 300
                model: pybackend.commitList
                delegate: Text {
                    text: model.display  // ou juste `modelData` si le rôle par défaut est utilisé
                    font.pixelSize: 14
                    // padding: 4        // CHANGED
                }
            }
        }
    }