Я работаю над графическим интерфейсом для создания и управления виртуальными средами для Python 3. Для этого я использую Python 3.7.4 и PyQt5. Я бы хотел, чтобы процесс создания виртуальной среды выполнялся мастером и с использованием метода create()
модуля Python venv
. Пока все работает как положено. Виртуальная среда создана правильно, и мастер переключается на следующую страницу.
Теперь, на этапе создания виртуальной среды (это происходит при переключении с первой страницы на вторую), я включил виджет, который отображает индикатор выполнения, чтобы соединить несколько секунд, пока venv
создает виртуальную среду. Это работает, но виджет показывает только черный контент, когда он появляется.
Я пытался исправить это с помощью потоков, а также с помощью многопроцессорной обработки (вызывая две функции одновременно), но это не сработало. Хотя виджет появляется, анимация не работает как обычно и уже на 100%, как только она видна. Также появляется после среда была создана.
Вот скриншот :
Вот части кода для воспроизведения :
from subprocess import Popen, PIPE, CalledProcessError
from venv import create
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtCore import (Qt, QRect, QSize, QMetaObject, QDir, QFile, QRegExp,
QBasicTimer)
from PyQt5.QtGui import (QIcon, QFont, QPixmap, QStandardItemModel,
QStandardItem)
from PyQt5.QtWidgets import (QMainWindow, QApplication, QAction, QHeaderView,
QFileDialog, QWidget, QGridLayout, QVBoxLayout,
QLabel, QPushButton, QSpacerItem, QSizePolicy,
QTableView, QAbstractItemView, QMenuBar, QMenu,
QStatusBar, QMessageBox, QWizard, QWizardPage,
QRadioButton, QCheckBox, QLineEdit, QGroupBox,
QComboBox, QToolButton, QProgressBar, QDialog,
QHBoxLayout)
#]===========================================================================[#
#] FIND INSTALLED INTERPRETERS [#============================================[#
#]===========================================================================[#
# look for installed Python versions in common locations
versions = ['3.9', '3.8', '3.7', '3.6', '3.5', '3.4', '3.3', '3']
notFound = []
versFound = []
pathFound = []
for i, v in enumerate(versions):
try:
# get installed python3 versions
getVers = Popen(["python" + v, "-V"],
stdout=PIPE, universal_newlines=True)
version = getVers.communicate()[0].strip()
# get paths of the python executables
getPath = Popen(["which", "python" + v],
stdout=PIPE, universal_newlines=True)
path = getPath.communicate()[0].strip()
versFound.append(version)
pathFound.append(path)
except (CalledProcessError, FileNotFoundError):
notFound.append(i)
Это индикатор выполнения :
#]===========================================================================[#
#] PROGRESS BAR [#===========================================================[#
#]===========================================================================[#
class ProgBarWidget(QWidget):
def __init__(self):
super().__init__()
self.initMe()
def initMe(self):
# basic window settings
self.setGeometry(600, 300, 300, 80)
self.setFixedSize(325, 80)
self.setWindowTitle("Creating")
self.setWindowFlag(Qt.WindowCloseButtonHint, False)
self.setWindowFlag(Qt.WindowMinimizeButtonHint, False)
horizontalLayout = QHBoxLayout(self)
verticalLayout = QVBoxLayout()
statusLabel = QLabel(self)
statusLabel.setText("Creating virtual environment...")
self.progressBar = QProgressBar(self)
self.progressBar.setFixedSize(300, 23)
self.timer = QBasicTimer()
self.timer.start(0, self)
self.i = 0
verticalLayout.addWidget(statusLabel)
verticalLayout.addWidget(self.progressBar)
horizontalLayout.addLayout(verticalLayout)
self.setLayout(horizontalLayout)
def timerEvent(self, e):
if self.i >= 100:
self.timer.stop()
#self.close()
self.i += 1
self.progressBar.setValue(self.i)
Это часть мастера :
#]===========================================================================[#
#] VENV WIZARD [#============================================================[#
#]===========================================================================[#
class VenvWizard(QWizard):
def __init__(self):
super().__init__()
self.setWindowTitle("Venv Wizard")
self.resize(535, 430)
self.move(578, 183)
self.setStyleSheet(
"""
QToolTip {
background-color: rgb(47, 52, 63);
border: rgb(47, 52, 63);
color: rgb(210, 210, 210);
padding: 2px;
opacity: 325
}
"""
)
self.addPage(BasicSettings())
self.addPage(InstallPackages())
self.addPage(Summary())
Первая страница мастера :
class BasicSettings(QWizardPage):
def __init__(self):
super().__init__()
folder_icon = QIcon.fromTheme("folder")
self.setTitle("Basic Settings")
self.setSubTitle("This wizard will help you to create and set up "
"a virtual environment for Python 3. ")
interpreterLabel = QLabel("&Interpreter:")
self.interprComboBox = QComboBox()
interpreterLabel.setBuddy(self.interprComboBox)
# add items from versFound to combobox
self.interprComboBox.addItem("---")
for i in range(len(versFound)):
self.interprComboBox.addItem(versFound[i], pathFound[i])
venvNameLabel = QLabel("Venv &name:")
self.venvNameLineEdit = QLineEdit()
venvNameLabel.setBuddy(self.venvNameLineEdit)
venvLocationLabel = QLabel("&Location:")
self.venvLocationLineEdit = QLineEdit()
venvLocationLabel.setBuddy(self.venvLocationLineEdit)
selectFolderToolButton = QToolButton()
selectFolderToolButton.setFixedSize(26, 27)
selectFolderToolButton.setIcon(folder_icon)
selectFolderToolButton.setToolTip("Browse")
# TODO: remove placeholder and add a spacer instead
placeHolder = QLabel()
# options groupbox
groupBox = QGroupBox("Options")
self.withPipCBox = QCheckBox("Install and update &Pip")
self.sysSitePkgsCBox = QCheckBox(
"&Make system (global) site-packages dir available to venv")
self.launchVenvCBox = QCheckBox(
"Launch a terminal with activated &venv after installation")
self.symlinksCBox = QCheckBox(
"Attempt to &symlink rather than copy files into venv")
# events
self.withPipCBox.toggled.connect(self.collectData)
self.sysSitePkgsCBox.toggled.connect(self.collectData)
self.launchVenvCBox.toggled.connect(self.collectData)
self.venvNameLineEdit.textChanged.connect(self.collectData)
self.venvLocationLineEdit.textChanged.connect(self.collectData)
self.interprComboBox.currentIndexChanged.connect(self.collectData)
self.symlinksCBox.toggled.connect(self.collectData)
selectFolderToolButton.clicked.connect(self.selectDir)
# store the collected values
self.interprVers = QLineEdit()
self.interprPath = QLineEdit()
self.venvName = QLineEdit()
self.venvLocation = QLineEdit()
self.withPip = QLineEdit()
self.sysSitePkgs = QLineEdit()
self.launchVenv = QLineEdit()
self.symlinks = QLineEdit()
# register fields
self.registerField("interprComboBox*", self.interprComboBox)
self.registerField("venvNameLineEdit*", self.venvNameLineEdit)
self.registerField("venvLocationLineEdit*", self.venvLocationLineEdit)
self.registerField("interprVers", self.interprVers)
self.registerField("interprPath", self.interprPath)
self.registerField("venvName", self.venvName)
self.registerField("venvLocation", self.venvLocation)
self.registerField("withPip", self.withPip)
self.registerField("sysSitePkgs", self.sysSitePkgs)
self.registerField("launchVenv", self.launchVenv)
self.registerField("symlinks", self.symlinks)
# grid layout
gridLayout = QGridLayout()
gridLayout.addWidget(interpreterLabel, 0, 0, 1, 1)
gridLayout.addWidget(self.interprComboBox, 0, 1, 1, 2)
gridLayout.addWidget(venvNameLabel, 1, 0, 1, 1)
gridLayout.addWidget(self.venvNameLineEdit, 1, 1, 1, 2)
gridLayout.addWidget(venvLocationLabel, 2, 0, 1, 1)
gridLayout.addWidget(self.venvLocationLineEdit, 2, 1, 1, 1)
gridLayout.addWidget(selectFolderToolButton, 2, 2, 1, 1)
gridLayout.addWidget(placeHolder, 3, 0, 1, 2)
gridLayout.addWidget(groupBox, 4, 0, 1, 3)
self.setLayout(gridLayout)
# options groupbox
groupBoxLayout = QVBoxLayout()
groupBoxLayout.addWidget(self.withPipCBox)
groupBoxLayout.addWidget(self.sysSitePkgsCBox)
groupBoxLayout.addWidget(self.launchVenvCBox)
groupBoxLayout.addWidget(self.symlinksCBox)
groupBox.setLayout(groupBoxLayout)
#]=======================================================================[#
#] SELECTIONS [#=========================================================[#
#]=======================================================================[#
def selectDir(self):
"""
Specify path where to create venv.
"""
fileDiag = QFileDialog()
folderName = fileDiag.getExistingDirectory()
self.venvLocationLineEdit.setText(folderName)
def collectData(self, i):
"""
Collect all input data.
"""
self.interprVers.setText(self.interprComboBox.currentText())
self.interprPath.setText(self.interprComboBox.currentData())
self.venvName.setText(self.venvNameLineEdit.text())
self.venvLocation.setText(self.venvLocationLineEdit.text())
# options
self.withPip.setText(str(self.withPipCBox.isChecked()))
self.sysSitePkgs.setText(str(self.sysSitePkgsCBox.isChecked()))
self.launchVenv.setText(str(self.launchVenvCBox.isChecked()))
self.symlinks.setText(str(self.symlinksCBox.isChecked()))
Вторая страница мастера :
class InstallPackages(QWizardPage):
def __init__(self):
super().__init__()
self.setTitle("Install Packages")
self.setSubTitle("Specify the packages which you want Pip to "
"install into the virtual environment.")
# ...
self.progressBar = ProgBarWidget()
def initializePage(self):
#interprVers = self.field("interprVers")
interprPath = self.field("interprPath")
self.venvName = self.field("venvName")
self.venvLocation = self.field("venvLocation")
self.withPip = self.field("withPip")
self.sysSitePkgs = self.field("sysSitePkgs")
#launchVenv = self.field("launchVenv")
self.symlinks = self.field("symlinks")
# overwrite with the selected interpreter
sys.executable = interprPath
# run the create process
self.createProcess()
# tried threading, but didn't really change the behaviour
#Thread(target=self.progressBar.show).start()
#Thread(target=self.createProcess).start()
def createProcess(self):
"""
Create the virtual environment.
"""
print("Creating virtual environment...") # print to console
self.progressBar.show() # the window containing the progress bar
# the create method from Python's venv module
create('/'.join([self.venvLocation, self.venvName]),
system_site_packages=self.sysSitePkgs,
symlinks=self.symlinks, with_pip=self.withPip)
self.progressBar.close() # close when done
print("Done.") # print to console when done
Последняя страница мастера (в данном случае не имеет значения.):
class Summary(QWizardPage):
def __init__(self):
super().__init__()
self.setTitle("Summary")
self.setSubTitle("...............")
# ...
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
ui = VenvWizard()
ui.show()
sys.exit(app.exec_())
Мои вопросы :
Является ли это правильным способом отображения индикатора хода между двумя QWizardPage
с?
Если нет, что может быть лучшим способом достижения этого?