我的研究包括:
PyQt reference to callable problem?
Python PyQt callback never runs - how to debug?
Passing extra arguments to PyQt slot
我正在构建一个Linux'启动器'程序目前有两个回调。一个只需启动点击的应用程序,另一个创建一个新的启动器。第一个工作正常 - 第二个工作非常棘手。我已经做了很多工作来解决这个问题。
我得到的错误是"属性错误:' QWidget'对象没有属性&new'auncher'"
以下是代码:(我很抱歉,如果它太长了 - 我最近被建议不要编辑太多)。
import sys, os
import subprocess
from functools import partial
from PyQt5.QtWidgets import QFileDialog, QToolButton, QHBoxLayout, QGridLayout, QSizePolicy, QSpacerItem, QWidget, QPushButton, QFormLayout, QLineEdit, QAction, QApplication, QDesktopWidget, QMainWindow, QTabWidget, QVBoxLayout
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QSize
from ruamel.yaml import YAML
yaml = YAML()
file_object = open("/home/tsc/PycharmProjects/launcher/Matrix.yaml", "r")
code = file_object.read()
matrix = yaml.load(code)
file_object.close()
class App(QMainWindow):
def __init__(self):
super(App, self).__init__()
self.initUI()
def launch(self, filepath):
subprocess.run(filepath)
def newLauncher(self):
num_butts = len(matrix)
btn_str = 'btn' + str(num_butts)
file_object = open("/home/tsc/PycharmProjects/launcher/Matrix.yaml", "a")
btn_str = 'btn' + str(num_butts + 1)
file_object.write("\n" + btn_str + ":\n")
self.setStyleSheet('padding: 3px; background: white');
fname, _ = QFileDialog.getOpenFileName(self, "select an executable or document to launch:", "",
"all files (*.*)")
path = fname
fname = os.path.basename(fname)
file_object.write(" " + "name: " + str(fname) + "\n" + " " + "path: " + str(path) + "\n")
self.setStyleSheet('padding: 3px; background: white');
icon, _ = QFileDialog.getOpenFileName(self, "select an image file for the icon:", "",
"all files (*.*)")
file_object.write(" " + "icon: " + str(icon) + "\n")
file_object.close()
def initUI(self):
super(App, self).__init__()
centralWidget = QWidget()
tabWidget = QTabWidget()
lay = QVBoxLayout(centralWidget)
for i in range(3):
page = QWidget()
pagelay = QGridLayout(page)
bmatrix = {}
for btn in matrix:
name = matrix[btn]['name']
filepath = matrix[btn]['path']
icon = matrix[btn]['icon']
bmatrix[btn] = QToolButton(page)
bmatrix[btn].setIcon(QIcon(icon))
bmatrix[btn].setIconSize(QSize(64, 64))
bmatrix[btn].resize(100, 100)
bmatrix[btn].clicked.connect(lambda checked, arg=filepath: self.launch(arg))
pagelay.addWidget(bmatrix[btn])
tabWidget.addTab(page, 'tab{}'.format(i))
mainMenu = self.menuBar()
fileMenu = mainMenu.addMenu('File')
mainMenu.addMenu(fileMenu)
newAction = QAction('&New', centralWidget)
#1 newAction.triggered.connect(lambda checked, arg=matrix: centralWidget.newLauncher(arg)) - shows window.
#2 newAction.triggered.connect(partial(self.NewLauncher, self)) - shows nothing, App has no NewLauncher
fileMenu.addAction(newAction)
editMenu = mainMenu.addMenu('Edit')
lay.addWidget(mainMenu)
lay.addWidget(tabWidget)
centralWidget.setGeometry(100, 100, 1080, 630)
centralWidget.setWindowTitle('LaunchMaster')
qtRectangle = centralWidget.frameGeometry()
centerPoint = QDesktopWidget().availableGeometry().center()
qtRectangle.moveCenter(centerPoint)
centralWidget.move(qtRectangle.topLeft())
centralWidget.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
这是yaml配置文件。如果要测试它,您需要自定义路径等。界面有一个menuBar和一个tabWidget,其中包含自己包含启动器按钮的页面。
Matrix.yaml:替换下划线的空格(缩进为2个字符。)。我还不确定这种标记语法,对不起麻烦。
btn1:
name: firefox
path: firefox-esr
icon: /home/tsc/PycharmProjects/launcher/icons/firefox.jpeg
btn2:
name: thunderbird
path: /home/tsc/thunderbird/thunderbird
icon: /home/tsc/PycharmProjects/launcher/icons/thunderbird.jpeg
答案 0 :(得分:0)
你问的问题(我认为)是这个已注释掉的代码:
newAction.triggered.connect(partial(self.NewLauncher, self))
评论说“没有显示,App没有NewLauncher”。
如果是这样,这里有两个问题。第一个是一个简单的拼写错误 - 你写了NewLauncher
而不是newLauncher
- 我假设你在实际测试时已经修复过了。第二个更深一点,你可能会遇到问题。
self.newLauncher
是一种绑定方法。也就是说,它知道它的意思是self
,当你调用它时,self
将作为第一个参数传入。如果你然后写partial(self.newLauncher, self)
,那么当它被调用时,它将与self.newLauncher(self)
做同样的事情 - 也就是说,它将传递两个副本{{1}作为单独的参数。
在self
来电AttributeError
时,错字会明显失败。connect
。但额外的self
只会在按钮点击信号内失败,并带有TypeError
。我认为,这意味着PyQt会向stderr写一些警告(你可能没有看到 - 特别是如果你在Windows上,甚至没有连接命令行窗口)并且没有为点击做任何事情。
你可能只想这样做:
newAction.triggered.connect(self.newLauncher)
有时,您希望将未绑定的方法从类对象(App.newLauncher
)和partial
传递给实例:
newAction.triggered.connect(partial(App.newLauncher, self))
...但在大多数情况下,包括这个,这只是一种不太可读(和较慢)的方式来做与传递绑定方法相同的事情。
答案 1 :(得分:0)
如果您不需要传递某些参数,则不必使用lambda函数,因此您可以定期连接。
另一方面,你不应该调用centralWidget.show(),但要显示,你还必须使用setCentralWidget设置centralWidget。
另一点是您必须验证用户是否选择了路径。
您的代码的另一项改进是使用QProcess.startDetached()
代替subprocess.run()
,因为它阻止了。
import sys
import os
from PyQt5.QtWidgets import QFileDialog, QToolButton, QHBoxLayout, QGridLayout, QSizePolicy, QSpacerItem, QWidget, QPushButton, QFormLayout, QLineEdit, QAction, QApplication, QDesktopWidget, QMainWindow, QTabWidget, QVBoxLayout
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QSize, QProcess
from ruamel.yaml import YAML
yaml_filename = "/home/tsc/PycharmProjects/launcher/Matrix.yaml"
yaml = YAML()
file_object = open(yaml_filename, "r")
code = file_object.read()
matrix = yaml.load(code)
file_object.close()
class App(QMainWindow):
def __init__(self):
super(App, self).__init__()
self.initUI()
def launch(self, filepath):
QProcess.startDetached(filepath)
def newLauncher(self):
fname, _ = QFileDialog.getOpenFileName(self, "select an executable or document to launch:", "",
"all files (*.*)")
if fname == "":
return
icon, _ = QFileDialog.getOpenFileName(self, "select an image file for the icon:", "",
"all files (*.*)")
if icon == "":
return
num_butts = len(matrix)
btn_str = 'btn' + str(num_butts)
file_object = open(yaml_filename, "a")
btn_str = 'btn' + str(num_butts + 1)
file_object.write("\n" + btn_str + ":\n")
path = fname
fname = os.path.basename(fname)
file_object.write(" " + "name: " + str(fname) + "\n" + " " + "path: " + str(path) + "\n")
file_object.write(" " + "icon: " + str(icon) + "\n")
file_object.close()
def initUI(self):
super(App, self).__init__()
centralWidget = QWidget()
tabWidget = QTabWidget()
lay = QVBoxLayout(centralWidget)
for i in range(3):
page = QWidget()
pagelay = QGridLayout(page)
bmatrix = {}
for btn in matrix:
name = matrix[btn]['name']
filepath = matrix[btn]['path']
icon = matrix[btn]['icon']
bmatrix[btn] = QToolButton(page)
bmatrix[btn].setIcon(QIcon(icon))
bmatrix[btn].setIconSize(QSize(64, 64))
bmatrix[btn].resize(100, 100)
bmatrix[btn].clicked.connect(lambda checked, arg=filepath: self.launch(arg))
pagelay.addWidget(bmatrix[btn])
tabWidget.addTab(page, 'tab{}'.format(i))
mainMenu = self.menuBar()
fileMenu = mainMenu.addMenu('File')
mainMenu.addMenu(fileMenu)
newAction = QAction('&New', centralWidget)
newAction.triggered.connect(self.newLauncher)
fileMenu.addAction(newAction)
editMenu = mainMenu.addMenu('Edit')
lay.addWidget(mainMenu)
lay.addWidget(tabWidget)
self.setGeometry(100, 100, 1080, 630)
self.setWindowTitle('LaunchMaster')
qtRectangle = self.frameGeometry()
centerPoint = QDesktopWidget().availableGeometry().center()
qtRectangle.moveCenter(centerPoint)
self.move(qtRectangle.topLeft())
self.setCentralWidget(centralWidget)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())