【问题标题】:How to trigger a method in a main Window from a dialog window in PyQt5?如何从 PyQt5 中的对话框窗口触发主窗口中的方法?
【发布时间】:2019-11-20 15:52:29
【问题描述】:

我是 PyQt 的初学者,并尝试使用用户在另一个对话框窗口中提供的信息来更新主窗口中的小部件。

这是我的主窗口:

class Window(QtWidgets.QMainWindow):    
    def __init__(self):        
        super(Window, self).__init__()
        uic.loadUi('GUI_MainWindow.ui',self)
        self.setWindowTitle("BR")

        self.statusBar()

        #save File Action
        saveFile= QtWidgets.QAction('&Profil speichern',self)
        saveFile.setShortcut('CTRL+S')
        saveFile.setStatusTip('Save File')
        saveFile.triggered.connect(self.file_save)

        mainMenu = self.menuBar()

        fileMenu = mainMenu.addMenu('&Datei')
        fileMenu.addAction(saveFile)

        self.home()

    def home(self):

        self.dlg = self.findChild(QtWidgets.QPushButton, 'addfile')
        self.dlg.clicked.connect(self.opensecondwindow)
        self.show()

    # this is the method that I want to call
    def updatebez(self,widgetname,filename):
        self.widg = self.findChild(QLabel, str(self.widgetname))
        self.widg.setText(filename)
        self.update()
        print(self.widg.Text())

    #calling the dialog window
    def opensecondwindow(self):
        self.sw = lesen(self)
        self.sw.show()

    def file_save(self):
        name, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save File',options=QFileDialog.DontUseNativeDialog)
        file = open(name, 'w')
        text = self.textEdit.toPlainText()
        file.write(text)
        file.close()

这是对话窗口:

class lesen(QtWidgets.QDialog):        
    def __init__(self,parent):
        global afl
        global calendar
        global fn
        super(lesen, self).__init__(parent)
        uic.loadUi('Kal.ui',self)
        self.setWindowTitle("Parametrierung")
        afl = self.findChild(QtWidgets.QLineEdit, 'Aufloesung')
        calendar = self.findChild(QtWidgets.QCalendarWidget, 'Calendar')

        self.addfile = self.findChild(QtWidgets.QPushButton, 'chooseFile')
        slot = self.findChild(QtWidgets.QSpinBox, 'spinBox')
        slotnr = slot.text()
        widgetname = 'ZRName'+ slotnr
        self.filename = self.findChild(QtWidgets.QLineEdit, 'Bez')
        self.addfile.clicked.connect(self.updatebez(widgetname,self.filename))
        self.addfile.clicked.connect(self.file_open)

    def Datenanpassung(self, tempfile):
        list=[]
        zeitabstand = int(afl.text())*60
        datum = calendar.selectedDate()
        a = datetime.datetime(datum.year(),datum.month(),datum.day(),00,00,00)
        for row in tempfile:
            a = a + datetime.timedelta(0,zeitabstand)
            datetimestr= str(a.date()) + ' ' + str(a.time())
            row = [datetimestr, row[0]]
            list.append(row)
        return list


    def file_open(self):
        #Dateiauswahl
        global name
        global tempfile
        global fn
        tempfile = []

        filters = ""
        selected_filter = "csv or json (*.csv *.json)"
        name, _ = QtWidgets.QFileDialog.getOpenFileName(self, 'Datei auswählen', filters, selected_filter,  
                                                        options=QFileDialog.DontUseNativeDialog)
        file = open(name, 'r', newline='')
        for row in csv.reader(file):
            tempfile.append(row)
        self.anpassen = self.findChild(QtWidgets.QCheckBox, 'checkBox')
        if self.anpassen.isChecked():
            newfile = self.Datenanpassung(tempfile)
            with open(os.path.basename(file.name)[:-4] +'_mit_DateTime.csv', 'w', newline='') as csvFile:
                writer = csv.writer(csvFile)
                writer.writerows(newfile)
            file = open(os.path.basename(file.name)[:-4] +'_mit_DateTime.csv', 'r', newline='')
            reader = csv.DictReader( file, fieldnames = ( "DateTime","Wert"))

            out = json.dumps(list(reader))
            f = open( os.path.basename(file.name)[:-4] +'_mit_DateTime.json', 'w')
            f.write(out)
        else:
            pass

编辑:我得到的错误是:(不知何故它只将第一行粘贴为代码)

Traceback (most recent call last):

  File "D:/Data/Zeitreihen_1.0x2.py", line 141, in opensecondwindow
    self.sw = lesen(self)

  File "D:/Data/Zeitreihen_1.0x2.py", line 35, in __init__
    self.addfile.clicked.connect(self.updatebez(widgetname,self.filename))

AttributeError: 'lesen' object has no attribute 'updatebez'


An exception has occurred, use %tb to see the full traceback.
SystemExit: 0

方法 updatez 背后的目标是根据用户在对话框窗口中的 QLineEdit 中键入的文本更新主窗口中的 QLabel 对象。 在添加方法并尝试在对话框窗口中调用它之前,一切正常。当我尝试单击显示对话框窗口的按钮时,现在会出现错误。

我知道最好的解决方案是为主窗口和对话窗口之间的信号设置一个新类,但我做错了。因此,我想知道是否可以让代码在不使用信号的情况下完成它必须做的事情。

在此先感谢各位互联网奇才们!

【问题讨论】:

  • 总是将完整的错误消息(从单词“Traceback”开始)作为文本(不是屏幕截图)放在有问题的(不是评论)中。还有其他有用的信息。

标签: python class methods pyqt5 signals


【解决方案1】:

您的代码存在各种问题,我会在回答时尝试解决这些问题。

该错误的主要原因是,正如错误报告的那样,您试图调用不存在的类实例的属性:updatebez 是主窗口的成员,而不是对话框的 (记住:self 是类实例的convention named reference)。

无论如何,即使已解决(通过调用parent.updatebez),它也不会起作用:信号/插槽连接通过使用可调用函数(插槽)来工作,因为连接是“交互式的”接口”,每次都必须根据信号的参数做出不同的反应;当您连接信号时,您实际上将 reference 传递给该函数(它尚未运行,也不应该运行!),并且该函数仅在发出该信号时才会被实际调用,可能使用信号参数作为其参数。
在您的情况下,您连接到一个函数 call,而不是它的引用,并且该调用可能会返回一些值或 None(所有不显式返回值的函数都隐式返回 None),这将导致连接错误:Qt 需要对可调用对象的引用,但由于该函数被就地调用,它取而代之取回其返回值,从而导致“未处理的 TypeError”异常。

“交互式界面”概念很重要:即使解决了上述问题,您也将始终以 slotnrwidgetname 变量的“静态”值结束。在 python 中,变量和属性通常是静态的:slotnr 永远不会在 spinbox 值改变时改变。

您需要创建一个函数,在每次单击按钮时“即时”计算widgetname,然后然后调用主窗口的函数。

class Lesen(QtWidgets.QDialog):
    def __init__(self, parent):
        # ...
        self.addfile.clicked.connect(self.sendUpdate)

    def sendUpdate(self):
        widgetname = 'ZRName'+ self.spinBox.text()
        self.parent().updatebez(widgetname, self.Bez.text()))

或者,可以使用lambda 函数。不过,您在使用它们时应该小心:虽然它们是有用的“一次性快捷方式”,但有时为了可读性和调试目的,最好编写一个实际函数。
最后但同样重要的是,lambda 是 anonymous 函数,因此如果您不保留对它们的引用,您将无法单独断开连接到它们的信号。

class Lesen(QtWidgets.QDialog):
    def __init__(self, parent):
        # ...
        self.addfile.clicked.connect(lambda: self.parent().updatebez(
            'ZRName' + self.spinBox.text(), self.Bez.text()))

关于上述示例和您的代码的一些说明:

  • 无需使用findChild 来获取您要查找的小部件;当使用loadUi(或self.setupUi(self)multiple inheritance approach)时,Qt 使用 Qt 对象名称自动为 UI 的所有对象创建属性名称:如果您的 QSpinBox 对象名称在 Designer 中是“spinBox”,您可以简单地访问它与self.spinBox;如果使用单继承方法,小部件将是 self.ui 的子级(因此,在上面的示例中为 self.ui.spinBox);
  • 由于上述原因,不需要每次调用updatebez函数槽时都设置self.widg
  • 使用setText()后无需调用update(),因为它会自动刷新标签;
  • 您可能希望在updatebez() (self.Bez.text()) 中使用行编辑text(),否则您将获得行编辑对象(不是字符串);
  • 如果对话框是持久的(不是modal),即使显示对话框,主窗口也将始终获得鼠标/键盘交互,这将导致用户能够打开多个对话框;你可能不希望这样;
  • 通常最好避免在__init__ 内调用show()
  • 虽然对于简单的情况,使用其他类实例的静态函数名称的实现没有任何问题,但通常首选接口,这就是信号和槽的存在:除了对象可重用性之外,主要好处是可读性、调试和进一步编辑(特别是在重构时);所以,是的,最好为对话框创建自己的信号,然后从主窗口连接它;
  • 正如PEP 8建议的那样:类名应始终大写,逗号后应始终有空格,类instances和变量名应小写(处理时可能与mixedCase与 Qt);

这是一个基于您的代码的示例,其中包含上面编写的所有内容(我只显示不同之处):

class Window(QtWidgets.QMainWindow):
    def __init__(self):
        super(Window, self).__init__()
        uic.loadUi('GUI_MainWindow.ui', self)
        self.setWindowTitle("BR")

        self.statusBar()

        #save File Action
        saveFile= QtWidgets.QAction('&Profil speichern', self)
        saveFile.setShortcut('CTRL+S')
        saveFile.setStatusTip('Save File')
        saveFile.triggered.connect(self.file_save)

        mainMenu = self.menuBar()

        fileMenu = mainMenu.addMenu('&Datei')
        fileMenu.addAction(saveFile)

        # create the dialog, just once
        self.lesenDialog = Lesen(self)
        self.sw.dataChanged.connect(self.updatebez)

        # show the dialog when clicking on the button, no need to create a
        # new one each time;
        self.addfile.clicked.connect(self.sw.show)

    def updatebez(self, index, filename):
        widgetname = 'ZRName{}'.format(index)
        widget = getattr(self, widgetname)
        widget.setText(filename)
        print(self.widg.Text())


class Lesen(QtWidgets.QDialog):
    dataChanged = QtCore.pyqtSignal(int, str)
    def __init__(self, parent):
        # ...
        self.addfile.clicked.connect(lambda: self.dataChanged.emit(
            self.spinBox.value(), self.Bez.text()))

最后,当您在 StackOverflow 上发布问题时,您应该使用some time to careful edit your examples(甚至可以编写全新的代码,这通常有助于在提出问题之前找到解决方案);此外,最好坚持使用英语并避免使用本地化的函数和变量名称,因为它确实提高了人们只关注您的问题的能力(因此更容易帮助您),而不是被可能对您无意义的名称分散注意力他们:你会惊讶于有那么多人因为这个原因而放弃帮助别人。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-06
    • 2019-01-01
    • 2020-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多