如何从 PyQt5 中的对话框 window 触发主要 Window 中的方法?
How to trigger a method in a main Window from a dialog window in PyQt5?
我是 PyQt 的初学者,正在尝试使用用户在另一个对话框 window 中提供的信息更新主要 window 中的小部件 window。
这是我的主window:
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()
这是对话框 window:
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 方法的目标是根据用户在对话框 window 中的 QLineEdit 中键入的文本更新主要 window 中的 QLabel 对象。
在添加方法并尝试在对话框 window 中调用它之前,一切正常。当我尝试单击显示对话框 window.
的按钮时,错误现在出现了
我知道最好的解决方案是在主 window 和对话框 window 之间设置一个新的 class 信号,但我做不到。因此我想知道是否可以让代码在不使用信号的情况下完成它必须做的事情。
提前致谢,互联网向导!
您的代码存在各种问题,我会在回答时尝试解决这些问题。
该错误的主要原因是,正如错误报告所述,您正在尝试调用不存在的 class 实例的属性:updatebez
是主要 window,而不是对话框的(记住:self
是 class 实例的 convention named reference)。
无论如何,即使解决了这个问题(通过调用 parent.updatebez
),它也无法正常工作:signal/slot 连接通过使用 callable 函数工作(插槽),因为连接 "interactive interfaces" 每次都必须根据信号的参数做出不同的反应;当你连接一个信号时,你实际上传递了一个 reference 给那个函数(它还没有 运行,也不应该!),并且只有当发出该信号,可能使用信号参数作为其参数。
在您的情况下,您连接到函数 call,而不是它的引用,并且该调用可能 return 某个值或 None(所有不显式 return 隐式 return None) 的值,这将导致连接错误:Qt expects a reference to a callable, but since the function is being called in place, it gets back它的 returned 值代替,导致 "unhandled TypeError" 异常。
"interactive interface" 概念很重要:即使解决了上述问题,slotnr
和 widgetname
变量的最终值总是 "static" .在 python 中,变量和属性通常是静态的:slotnr
如果旋转框值发生变化,则永远不会改变。
您需要创建一个函数,在每次单击按钮时计算 widgetname
"on the fly",然后 然后 调用主函数window.
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
函数。不过,您在使用它们时应该小心:虽然它们很有用 "one-timer shortcuts",但出于可读性和调试目的,有时最好编写一个实际的函数。
最后但并非最不重要的一点是,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 对象名称是 "spinBox" 在 Designer 中,您可以简单地使用 self.spinBox
访问它;如果使用单继承方法,小部件将是 self.ui
的子项(因此,在上面的示例中为 self.ui.spinBox
);
- 由于上面的原因,不需要每次调用
updatebez
函数槽时都设置self.widg
;
- 使用
setText()
后不需要调用update()
,因为它会自动刷新标签;
- 您可能希望在
updatebez()
(self.Bez.text()
) 中使用行编辑 text()
,否则您将获得行编辑对象(不是字符串) ;
- 如果对话框是持久的(不是 modal),即使显示对话框,主 window 也将始终获得 mouse/keyboard 交互,这将导致用户能够打开多个对话框;你可能不想要那个;
- 通常最好避免在
__init__
内调用 show()
;
- 虽然,对于简单的情况,使用 other class 实例 的静态函数名称的实现没有任何问题,通常首选接口,这就是信号和插槽存在于:除了对象的可重用性之外,主要的好处是可读性、调试和进一步编辑(特别是在重构时);所以,是的,最好为对话框创建自己的信号,然后从主 window;
连接它
- as PEP 8 建议:class 名称应始终大写,逗号后应始终有一个 space,并且 class instancies 和变量名称应该是小写的(在处理 Qt 时可能使用
mixedCase
);
这是一个基于您的代码的示例,其中包含上面编写的所有内容(我只显示不同之处):
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()))
最后,在 Whosebug 上发布问题时,您应该 some time to careful edit your examples(甚至是编写全新的代码,这通常有助于在提问之前找到解决方案);此外,坚持使用英语并避免本地化的函数和变量名总是更好,因为它确实提高了人们专注于你的问题的能力(因此更容易帮助你),而不是被可能毫无意义的名称分散注意力他们:你会惊讶于有多少人因为这个原因而放弃帮助某人。
我是 PyQt 的初学者,正在尝试使用用户在另一个对话框 window 中提供的信息更新主要 window 中的小部件 window。
这是我的主window:
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()
这是对话框 window:
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 方法的目标是根据用户在对话框 window 中的 QLineEdit 中键入的文本更新主要 window 中的 QLabel 对象。 在添加方法并尝试在对话框 window 中调用它之前,一切正常。当我尝试单击显示对话框 window.
的按钮时,错误现在出现了我知道最好的解决方案是在主 window 和对话框 window 之间设置一个新的 class 信号,但我做不到。因此我想知道是否可以让代码在不使用信号的情况下完成它必须做的事情。
提前致谢,互联网向导!
您的代码存在各种问题,我会在回答时尝试解决这些问题。
该错误的主要原因是,正如错误报告所述,您正在尝试调用不存在的 class 实例的属性:updatebez
是主要 window,而不是对话框的(记住:self
是 class 实例的 convention named reference)。
无论如何,即使解决了这个问题(通过调用 parent.updatebez
),它也无法正常工作:signal/slot 连接通过使用 callable 函数工作(插槽),因为连接 "interactive interfaces" 每次都必须根据信号的参数做出不同的反应;当你连接一个信号时,你实际上传递了一个 reference 给那个函数(它还没有 运行,也不应该!),并且只有当发出该信号,可能使用信号参数作为其参数。
在您的情况下,您连接到函数 call,而不是它的引用,并且该调用可能 return 某个值或 None(所有不显式 return 隐式 return None) 的值,这将导致连接错误:Qt expects a reference to a callable, but since the function is being called in place, it gets back它的 returned 值代替,导致 "unhandled TypeError" 异常。
"interactive interface" 概念很重要:即使解决了上述问题,slotnr
和 widgetname
变量的最终值总是 "static" .在 python 中,变量和属性通常是静态的:slotnr
如果旋转框值发生变化,则永远不会改变。
您需要创建一个函数,在每次单击按钮时计算 widgetname
"on the fly",然后 然后 调用主函数window.
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
函数。不过,您在使用它们时应该小心:虽然它们很有用 "one-timer shortcuts",但出于可读性和调试目的,有时最好编写一个实际的函数。
最后但并非最不重要的一点是,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 对象名称是 "spinBox" 在 Designer 中,您可以简单地使用self.spinBox
访问它;如果使用单继承方法,小部件将是self.ui
的子项(因此,在上面的示例中为self.ui.spinBox
); - 由于上面的原因,不需要每次调用
updatebez
函数槽时都设置self.widg
; - 使用
setText()
后不需要调用update()
,因为它会自动刷新标签; - 您可能希望在
updatebez()
(self.Bez.text()
) 中使用行编辑text()
,否则您将获得行编辑对象(不是字符串) ; - 如果对话框是持久的(不是 modal),即使显示对话框,主 window 也将始终获得 mouse/keyboard 交互,这将导致用户能够打开多个对话框;你可能不想要那个;
- 通常最好避免在
__init__
内调用show()
; - 虽然,对于简单的情况,使用 other class 实例 的静态函数名称的实现没有任何问题,通常首选接口,这就是信号和插槽存在于:除了对象的可重用性之外,主要的好处是可读性、调试和进一步编辑(特别是在重构时);所以,是的,最好为对话框创建自己的信号,然后从主 window; 连接它
- as PEP 8 建议:class 名称应始终大写,逗号后应始终有一个 space,并且 class instancies 和变量名称应该是小写的(在处理 Qt 时可能使用
mixedCase
);
这是一个基于您的代码的示例,其中包含上面编写的所有内容(我只显示不同之处):
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()))
最后,在 Whosebug 上发布问题时,您应该 some time to careful edit your examples(甚至是编写全新的代码,这通常有助于在提问之前找到解决方案);此外,坚持使用英语并避免本地化的函数和变量名总是更好,因为它确实提高了人们专注于你的问题的能力(因此更容易帮助你),而不是被可能毫无意义的名称分散注意力他们:你会惊讶于有多少人因为这个原因而放弃帮助某人。