1

我的研究包括:

PyQt 对可调用问题的引用?

Python PyQt 回调从不运行 - 如何调试?

将额外的参数传递给 PyQt 插槽

我正在构建一个当前有两个回调的 linux“启动器”程序。一个简单地启动点击的应用程序,另一个创建一个新的启动器。第一个工作正常 - 第二个非常棘手。我已经做了很多来解决这个问题。

  1. 在脚本上运行 PyCharm 调试并观察自己的值等以了解更多信息
  2. 将 NewLauncher 函数移动到 InitUI 方法中。
  3. 无休止地改变“self”、“centralWidget”和其他对象引用。
  4. 使用 functools 部分。

我得到的错误是“AttributeError:'QWidget'对象没有属性'newLauncher'”

这是代码:(如果它太长,我很抱歉 - 我最近被建议不要编辑太多)。

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  
4

2 回答 2

1
  • 如果您不需要传递某些参数,则不必使用 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_())
于 2018-03-12T00:32:12.760 回答
0

你要问的那一行(我认为)是这个注释掉的代码:

newAction.triggered.connect(partial(self.NewLauncher, self))

评论说“什么都不显示,App 没有 NewLauncher”。

如果是这样,这里有两个问题。第一个是一个简单的错字——你写NewLauncher的不是newLauncher——我假设你在实际测试时已经修正了它。第二个更深一些,你可能会遇到问题。

self.newLauncher是绑定方法。也就是说,它知道self它的用途,并且当您调用它时,self它将作为第一个参数传入。如果你然后 write partial(self.newLauncher, self),当它被调用时,它会做同样的事情self.newLauncher(self)——也就是说,它会传入两个副本self作为单独的参数。

错字将非常明显地失败,并AttributeErrorconnect通话中出现。但是额外的self只会TypeError在按钮点击信号内失败,并带有 , 。我相信,这意味着 PyQt 会向 stderr 写入一些警告(您可能不会看到这些警告——尤其是如果您在 Windows 上并且甚至没有附加命令行窗口)并且对单击不做任何事情。

您可能只想这样做:

newAction.triggered.connect(self.newLauncher)

有时,您希望将类对象 ( )中的未绑定方法传递给实例:App.newLauncherpartial

newAction.triggered.connect(partial(App.newLauncher, self))

......但在大多数情况下,包括这个,这只是一种可读性较差(且速度较慢)的方式来做与传递绑定方法相同的事情。

于 2018-03-12T00:28:12.583 回答