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
}
}
}
}
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
}
}
}
}