0

I'm a beginner with PyQt and trying to update widgets in the main window with information that's given by the user in another dialog window.

this is my main 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()

and this is the dialog 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

Edit: the error I get is: (somehow it only pastes the first line as code)

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

the goal behind the method updatez is to update a QLabel object in the main window from a text that the user typed in QLineEdit in the dialog window. Before adding the method and trying to call it in the Dialog window, everything was working fine. the error now shows up when i try to click on the button that shows the dialog window.

I know the best solution would be setting up a new class for a signal between both the main window and the dialog window but I couldn't get it right . Therefore I would like to know if it is possible to make the code do what it has to do without using a signal.

Thanks in advance, internet wizards!

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Walid
  • 3
  • 4
  • always put full error message (starting at word "Traceback") in question (not comment) as text (not screenshot). There are other useful information. – furas Nov 20 '19 at 15:56

1 Answers1

2

There are various issues with your code, I'll try to address them while answering.

The main reason for that error is that, as the error reports, you're trying to call an attribute of a class instance that doesn't exists: updatebez is a member of the main window, not the dialog's (remember: self is a convention named reference to the class instance).

Anyway, even if that was addressed (by calling parent.updatebez), it wouldn't work anyway: signal/slot connections work by using callable functions (slots), as connections are "interactive interfaces" that have to react differently each time according to the signal's parameters; when you connect a signal, you actually pass a reference to that function (it has not run yet, nor it should!), and that function will be actually called only when that signal is emitted, possibly using the signal arguments as its parameters.
In your case, you connected to a function call, not its reference, and that call will probably return some value or None (all functions that don't explictly return a value implicitly return None), which will result in a connection error: Qt expects a reference to a callable, but since the function is being called in place, it gets back its returned value instead, resulting in an "unhandled TypeError" exception.

The "interactive interface" concept is important: even if the above issues were addressed, you'll always end with a "static" value for the slotnr and widgetname variables. In python, variables and attributes are normally static: slotnr will never be changed if the spinbox value changes.

You will need to create a function that computes the widgetname "on the fly" each time the button is clicked, and then call the function of the main 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()))

Alternatively, lambda functions can be used. You should be careful in using them, though: while they are useful "one-timer shortcuts", sometimes it's better to write an actual function instead, for both readability and debugging purposes.
Last but not least, lambdas are anonymous functions, so if you don't keep a reference to them, you won't be able to disconnect signals connected to them individually.

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

Some notes about the examples above and your code:

  • there is no need to use findChild to get the widget you're looking for; when using loadUi (or self.setupUi(self) with the multiple inheritance approach) Qt automatically creates attribute names for all objects of the UI using their Qt object name: if your QSpinBox object name is "spinBox" in Designer, you can simply access it with self.spinBox; if the single inheritance approach is used, the widgets will be children of self.ui (so, self.ui.spinBox in the example above);
  • for the reason above, there is no need to set self.widg each time the updatebez function slot is called;
  • calling update() is not necessary after using setText(), as it will automatically refresh the label;
  • you'll probably want to use the line edit text() within updatebez() (self.Bez.text()), otherwise you'll get the line edit object (which is not a string);
  • if the dialog is persistent (not modal), the main window will always get mouse/keyboard interaction even if the dialog is shown, which will result in the user being able to open multiple dialogs; you probably don't want that;
  • it's usually better to avoid calling show() within __init__;
  • while, for simple situations, there's nothing wrong with implementations that use static function names of other class instancies, interfaces are usually preferred, and that's what signals and slot exist for: besides object reusability, the major benefit are readability, debugging and further editing (expecially when refactoring); so, yes, it's better to create your own signal for the dialog and then connect it from the main window;
  • as PEP 8 suggests: class names should always be capitalized, there should always be a space after commas, and class instancies and variables names should be lower case (possibly with mixedCase when dealing with Qt);

Here's an example based on your code that includes everything written above (I'm only showing the differencies):

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()))

Finally, when posting a question on StackOverflow you should take some time to careful edit your examples (even to the point of writing brand new code, which often helps to find the solution even before asking at all); also, it's always better to stick to English and avoid localized function and variable names, as it really improves people's ability to focus just on your problem (thus being more prone to help you), instead of being distracted by names that are probably meaningless to them: you'd be surprised by the amount of people giving up in helping somebody because of this reason.

musicamante
  • 41,230
  • 6
  • 33
  • 58