PyQt5:一个回调有效,另一个没有 - 为什么?

时间:2018-03-12 00:01:16

标签: python pyqt pyqt5 qaction

我的研究包括:

PyQt reference to callable problem?

Python PyQt callback never runs - how to debug?

Passing extra arguments to PyQt slot

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

  1. 在脚本上运行PyCharm debug并观察self等的值以了解更多信息
  2. 将NewLauncher函数移动到InitUI方法中。
  3. 无休止地改变" self"," centralWidget"和其他对象引用。
  4. 使用functools partial。
  5. 我得到的错误是"属性错误:' 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  
    

2 个答案:

答案 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_())