tkinter: Widget aus anderem Modul heraus ändern?

Fragen zu Tkinter.
Antworten
Phillip Pendulina
User
Beiträge: 5
Registriert: Freitag 17. Mai 2019, 17:51

Hallo,
ich habe eine Einsteigerfrage zu tkinter.

Wegen der Übersichtichkeit habe ich mein Python-Programm auf mehrere Module aufgeteilt:
in einer Datei Hauptprogramm.py habe ich das tkinter- Fenster definiert.
Beim Drücken der Button werden Funktionen ausgeführt, die in anderen Dateien (Schritt1.py, Schritt2.py usw.) stehen. Das funktioniert soweit.

Jetzt möchte ich beim Abarbeiten der Funktionen den Text eines Labels im tkinter- Fenster ändern,
um den Benutzer über den Stand der Abarbeitung zu informieren. Das gelingt mir aber nicht.

Meine Frage:
Wie kann ich aus einem Modul (einer *.py- Datei) ein Widget in einem tkinter- Fenster ansprechen, das in einem anderen Modul erstellt wurde?
Oder ist mein ganzer Ansatz falsch?

Danke für Eure Unterstützung

Phillip
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das geht, wenn man Objekt orientiert arbeitet. Du kannst problemlos eine Klasse aus einem Module instanziieren, die das Widget als Argument übergeben bekommt. Alternativ, aber ein bisschen knödelig und auf Dauer keine gute Idee: mit functools.partial zb ein button command erzeugen, welches die notwendigen Widgets wund anderen Parameter “angebunden” bekommt.
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Phillip Pendulina: Das klingt so als hättest Du Variablen auf Modulebene definiert. Da gehört aber nur Code hin der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Alles was eine Funktion oder Methode ausser Konstanten benötigt, sollte sie als Argument(e) übergeben bekommen.

Wenn man ein Programm auf mehrere Module aufteilt, sollte man die übrigens auch gleich in einem Package organisieren, so das die Modulnamen nicht auf ”oberster Ebene” mit allen anderen installierten Modulen und Packages in Konkurrenz stehen und Namenskollisionen entstehen können.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Phillip Pendulina
User
Beiträge: 5
Registriert: Freitag 17. Mai 2019, 17:51

@__deets__:
Ich werde mich also als Nächstes mal genauer mit den Klassen beschäftigen.

@__blackjack__:
ja, ich habe einige Variablen auch ausserhalb der Funktionen definiert. Danke für den Tip, daß man das vermeiden soll.


Danke erstmal für Eure Antworten.

Viele Grüße- Phillip
Phillip Pendulina
User
Beiträge: 5
Registriert: Freitag 17. Mai 2019, 17:51

Hallo nochmal,
so richtig komme ich noch nicht weiter. Vielleicht könnt Ihr mich ja nochmal in die richtige Richtung schubsen :)

Ich möchte mir einen sauberen Programmierstil angewöhnen und deshalb GUI von der Logik trennen.
Es gelingt mir trotzdem noch nicht, von ausserhalb der GUI- Klasse auf die Elemente dieser GUI- Klasse zuzugreifen.

Als Beispiel habe ich eine Klasse "Window" für die GUI, ein mit dem Qt-Creator erzeugtes ui- File,
und eine Datei MeineExternenFunktionen.py

Enthalten sind drei LineEdits und zwei Buttons. In die LineEdits gebe ich zwei ganze Zahlen ein, durch Klick auf den Button
erfolgt die Addition und die Ausgabe der Summe im dritten LineEdit.

Führe ich die Addition innerhalb der GUI- Klasse "Window" aus, klappt alles.
Führe ich die Addition ausserhalb der der GUI- Klasse "Window", also im Modul MeineExternenFunktionen, aus,
- wo ich sie eigentlich haben möchte- bekomme ich Fehlermeldungen.
Mein Problem ist, daß ich nicht weiß, wie ich das LineEdit- Widget in der Klasse "Window" ansprechen soll.

Vielleicht habt Ihr ja einen Tip für mich?

Phillip

PS: Hat jetzt nichts mehr direkt mit tkinter zu tun, ich wollte aber keinen zweiten Thread dazu aufmachen


Mein "Hauptprogramm":

Code: Alles auswählen


import sys, os
from PyQt5 import QtGui, QtCore, QtWidgets, uic
import MeineExternenFunktionen

DIRPATH = os.path.join(os.path.dirname(os.path.abspath(__file__)))

class Window(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        uic.loadUi(os.path.join(DIRPATH, 'oberflaeche.ui'), self)
        self.btSummeIntern.clicked.connect(self.summe_intern)
        self.btSummeExtern.clicked.connect(MeineExternenFunktionen.summe_extern)


    
    def summe_intern(self):
        SummandEins=self.leSummandEins.text()
        SummandZwei=self.leSummandZwei.text()
        Summe=str(int(SummandEins)+int(SummandZwei))
        self.leSumme.setText(Summe)


if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())
    
    


=========================================================================================================
Meine Datei MeineExternenFunktionen.py:

Code: Alles auswählen


import sys, os


def summe_extern(self):
     SummandEins=self.leSummandEins.text()
     SummandZwei=self.leSummandZwei.text()
     Summe=str(int(SummandEins)+int(SummandZwei))
     self.leSumme.setText(Summe)
    


=========================================================================================================

... und mein ui- File oberflaeche.ui:

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Window</class>
 <widget class="QWidget" name="Window">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>791</width>
    <height>179</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Hello World</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout">
   <item>
    <widget class="QTabWidget" name="tabWidget">
     <property name="sizePolicy">
      <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
       <horstretch>0</horstretch>
       <verstretch>0</verstretch>
      </sizepolicy>
     </property>
     <property name="currentIndex">
      <number>0</number>
     </property>
     <widget class="QWidget" name="tab">
      <attribute name="title">
       <string>Tab 1</string>
      </attribute>
      <widget class="QLabel" name="lbLabel">
       <property name="geometry">
        <rect>
         <x>90</x>
         <y>180</y>
         <width>451</width>
         <height>16</height>
        </rect>
       </property>
       <property name="text">
        <string>TextLabel</string>
       </property>
      </widget>
      <widget class="QPushButton" name="btTestIntern">
       <property name="geometry">
        <rect>
         <x>560</x>
         <y>170</y>
         <width>75</width>
         <height>23</height>
        </rect>
       </property>
       <property name="text">
        <string>Test Intern</string>
       </property>
      </widget>
      <widget class="QPushButton" name="btTestExtern">
       <property name="geometry">
        <rect>
         <x>560</x>
         <y>200</y>
         <width>75</width>
         <height>23</height>
        </rect>
       </property>
       <property name="text">
        <string>Test extern</string>
       </property>
      </widget>
      <widget class="QLineEdit" name="leSummandEins">
       <property name="geometry">
        <rect>
         <x>30</x>
         <y>40</y>
         <width>113</width>
         <height>20</height>
        </rect>
       </property>
      </widget>
      <widget class="QLineEdit" name="leSummandZwei">
       <property name="geometry">
        <rect>
         <x>170</x>
         <y>40</y>
         <width>113</width>
         <height>20</height>
        </rect>
       </property>
      </widget>
      <widget class="QLineEdit" name="leSumme">
       <property name="geometry">
        <rect>
         <x>310</x>
         <y>40</y>
         <width>113</width>
         <height>20</height>
        </rect>
       </property>
      </widget>
      <widget class="QLabel" name="label">
       <property name="geometry">
        <rect>
         <x>30</x>
         <y>70</y>
         <width>47</width>
         <height>13</height>
        </rect>
       </property>
       <property name="text">
        <string>Summand</string>
       </property>
      </widget>
      <widget class="QLabel" name="label_2">
       <property name="geometry">
        <rect>
         <x>170</x>
         <y>70</y>
         <width>47</width>
         <height>13</height>
        </rect>
       </property>
       <property name="text">
        <string>Summand</string>
       </property>
      </widget>
      <widget class="QLabel" name="label_3">
       <property name="geometry">
        <rect>
         <x>310</x>
         <y>70</y>
         <width>47</width>
         <height>13</height>
        </rect>
       </property>
       <property name="text">
        <string>Summe</string>
       </property>
      </widget>
      <widget class="QPushButton" name="btSummeIntern">
       <property name="geometry">
        <rect>
         <x>460</x>
         <y>32</y>
         <width>91</width>
         <height>31</height>
        </rect>
       </property>
       <property name="text">
        <string>Summe intern</string>
       </property>
      </widget>
      <widget class="QPushButton" name="btSummeExtern">
       <property name="geometry">
        <rect>
         <x>460</x>
         <y>70</y>
         <width>91</width>
         <height>31</height>
        </rect>
       </property>
       <property name="text">
        <string>Summe extern</string>
       </property>
      </widget>
     </widget>
     <widget class="QWidget" name="tab_2">
      <attribute name="title">
       <string>Tab 2</string>
      </attribute>
     </widget>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Phillip Pendulina: Auf die `QLineEdit`\s sollst Du in der Funktion ja gar nicht zugreifen wollen. Das ist ja genau die Trennung von GUI und Logik, das in der Logik keine GUI-Objekte mehr vorkommen. Da die Logik nur aus einer Addition besteht, sollte die Funktion so aussehen:

Code: Alles auswählen

def summe_extern(summand_a, summand_b):
    return summand_a + summand_b
Und in der GUI brauchst Du eine Funktion oder Methode die sich um den GUI-Teil kümmert der passieren soll wenn der Benutzer auf die Schaltfläche klickt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Phillip Pendulina
User
Beiträge: 5
Registriert: Freitag 17. Mai 2019, 17:51

Danke für die schnelle Antwort.
Deine Argumente leuchten mir ein.
Wie kann ich dann aber das Ergebnis der Addition anzeigen lassen? Muss sich meine externe Funktion irgendwie bei der GUI zurück melden, wenn sie die Addition beendet hat?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wieso muss die sich melden? Sie bekommt Argumente und gibt das Ergebnis zurück. An der aufrufenden Stelle werden die Parameter aus GUI-Elementen gezogen. Und dann das Ergebnis wieder verarbeitet und zb in ein Label gesteckt.
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Phillip Pendulina: Noch ein paar Anmerkungen zum Quelltext:

Nicht alles was aus `PyQt5` importiert wird, wird auch tatsächlich verwendet.

Anstelle von `os.path`-Funktionen würde ich in neuem Code das `pathlib`-Modul verwenden. Der Code ist damit oft kürzer und leichter verständlich.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Und externe Bibliotheken die Code in Programmiersprachen oder von Projekten einbinden wo es andere Konventionen gibt. Also zum Beispiel die Namen bei Qt, die einer üblichen Konvention bei C++-Programmierern entsprechen, und wo es mehr Aufwand als Sinn macht die für die Python-Anbidnung alle zu ändern.

Kryptische Prä- oder Suffixe sollte man nicht verwenden. Also konkret hier `bt` oder `le`.

In den GUI-Teil gehört auch die Umwandlung und Validierung der Eingabe und die Rückmeldung an den Benutzer wenn es fehlerhafte Daten gibt.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
from pathlib import Path
import sys

from PyQt5 import QtWidgets, uic

from MeineExternenFunktionen import summe_extern

DIR_PATH = Path(__file__).absolute().parent


class Window(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        uic.loadUi(str(DIR_PATH / "oberflaeche.ui"), self)
        self.summe_intern_button.clicked.connect(self.summe_intern)
        self.summe_extern_button.clicked.connect(self.summe_extern)

    def summe_intern(self):
        try:
            summand_eins = int(self.summand_eins_edit.text())
            summand_zwei = int(self.summand_zwei_edit.text())
        except ValueError:
            result = "Fehler! Bitte Zahlen eingeben."
        else:
            result = str(summand_eins + summand_zwei)
        
        self.summe_edit.setText(result)

    def summe_extern(self):
        try:
            summand_eins = int(self.summand_eins_edit.text())
            summand_zwei = int(self.summand_zwei_edit.text())
        except ValueError:
            result = "Fehler! Bitte Zahlen eingeben."
        else:
            result = str(summe_extern(summand_eins, summand_zwei))

        self.summe_edit.setText(result)


def main():
    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Phillip Pendulina
User
Beiträge: 5
Registriert: Freitag 17. Mai 2019, 17:51

Danke Euch beiden.

Jetzt habe ich verstanden, wie Funktionsaufruf und Weiterverarbeitung des Ergebnisses aussehen sollten.
Manchmal ist ein Stück Programmcode verständlicher als die Theorie, danke deshalb auch für das Codebeispiel.
Die Hinweise zum guten Programmierstil (Namenskonventionen) werde ich versuchen zu beachten :-)

Viele Grüße- Phillip
Antworten