Text wird nicht übernommen

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
ThePaulCraft
User
Beiträge: 11
Registriert: Samstag 10. Oktober 2020, 17:43

Hallo, ich bin gerade dabei ein kleines Programm zu basteln in dem Rezepte angezeigt werden, wenn ich das ganze ausführe funktioniert es auch soweit, allerdings haben alle Rezepte den gleichen Text. Ich vermute, dass das daran liegt, dass ich eine lambda-function verwende. Allerdings weiß ich nicht wie ich das ganze anders lösen kann bzw. wie man den jetzigen Fehler beheben kann. Hier mein Code:

Code: Alles auswählen

from PyQt5.QtWidgets import QWidget, QApplication, QGridLayout, QLabel, QPushButton
from PyQt5 import QtCore
from PyQt5.QtGui import QIcon

import sys

import glob

app = QApplication(sys.argv)
app.setStyle("Fusion")


app.setApplicationName("Rezepte")

win = QWidget()
win.setStyleSheet(open("style.css").read())
win.setWindowIcon(QIcon("./icon.png"))
grid = QGridLayout()

def frame1():
	global win, grid

	for i in reversed(range(grid.count())):
		grid.itemAt(i).widget().deleteLater()

	
	buttons = []

	for f in glob.glob("recipes/*.txt"):
		b = QPushButton(win)
		b.setText(f.replace(".txt", "").replace("recipes\\", ""))
		print(f)
		file = open(f)
		rezept = file.read()
		file.close()
		print(rezept)
		b.clicked.connect(lambda: recipe_frame(rezept))
		buttons.append(b)

	for b in buttons:
		b.setStyleSheet(open("style_buttons.css").read())

	title = QLabel("🍔Rezepte🍕")
	title.setAlignment(QtCore.Qt.AlignCenter)
	title.setStyleSheet("font-size: 30px;\nfont-family: Arial;")

	grid.addWidget(title, 0, 0)
	for i, b in enumerate(buttons, start=1):
		grid.addWidget(b, i, 0)

	win.setLayout(grid)

def recipe_frame(recipe: str):
	global win, grid

	for i in reversed(range(grid.count())):
		grid.itemAt(i).widget().deleteLater()

	buttons = []

	back_button = QPushButton(win)
	back_button.setText("Zurück")

	back_button.clicked.connect(frame1)

	buttons.append(back_button)

	for b in buttons:
		b.setStyleSheet(open("style_buttons.css").read())
	
	recipe_label = QLabel(recipe)

	grid.addWidget(back_button, 0, 0)
	grid.addWidget(recipe_label, 1, 0)

	win.setLayout(grid)


frame1()

win.show()

sys.exit(app.exec_())
Alle Rezepte sind im Unterornder "recipes" als .txt Datein abgespeichert. Ich entschuldige mich jetzt schonmal für meinen Codestil und meine Rechtschreibung :D
mfg
ThePaulCraft
User
Beiträge: 11
Registriert: Samstag 10. Oktober 2020, 17:43

hat sich geklärt, das problem lässt sich mit der partital klasse aus functools lösen.
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ThePaulCraft: Anmerkungen zum Quelltext:

Eingerückt wird mit vier Leerzeichen pro Ebene, nicht mit Tabs.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Dateien die man öffnet, sollte man auch wieder schliessen. Am besten verwendet man wo das möglich ist, die ``with``-Anweisung beim öffnen. Bei Textdateien sollte man explizit die Kodierung angeben.

Falls man den gesamten Dateiinhalt in eine Zeichenkette lesen möchte, hat `pathlib.Path` dafür eine Methode. Das sollte man auch anstelle vom `glob`-Modul verwenden.

Das Icon würde ich eher auf dem `QApplication`-Objekt setzen als auf individuellen Fenstern.

Vergiss das es ``global`` gibt. Funktionen und Methoden bekommen alles was sie ausser Konstanten benötigen als Argument(e) übergeben.

`QWidget.setLayout()` sollte nur einmal aufgerufen werden, das macht gar keinen Sinn das immer mit dem selben `QLayout`-Objekt aufzurufen. Und wenn man `QWidget`\s in ein `QLayout` steckt, dann übernimmt das die Elternrolle, man braucht also `win` in den Funktionen nicht.

Funktions- und Methodennamen beschreiben üblicherweise die Tätigkeit die von der Funktion oder Methode durchgeführt wird. Der Leser weiss dann was die tun und kann sie von eher passiven Werten unterscheiden.

`frame1` und `recipe_frame` sind keine Tätigkeiten. Und bei `frame1` ist auch die 1 unsinnig.

Beide Funktionen löschen als erstes alle Elemente aus dem Layout, wobei ich mir nicht sicher bin, dass das so unfallfrei funktioniert, denn es wird zwar `deleteLater()` auf den `QWidget`-Objekten aufgerufen, aber die `QLayoutItem`-Objekte werden nicht gelöscht. Damit handelt man sich eventuell Probleme/ein Speicherleck ein. Die Qt-Dokumentation zeigt zum sicheren entfernen ein Codeschnippsel in dem solange ``takeAt(0)`` auf dem `QLayout`-Objekt aufgerufen wird, bis das einen Nullpointer liefert. Und das sollte man in eine eigene Funktion auslagern.

Pfad und Dateinamen mit Zeichenkettenoperationen zu bearbeiten ist kaputt. Auch hier ist `pathlib.Path` hilfreich. Das hat ganz einfach ein Attribut für den Dateinamen ohne Dateinamenserweiterung.

Im Grunde werden die Rezeptdateien in einer zufälligen Reihenfolge aufgelistet. Denn in welcher Reihenfolge die geliefert werden ist nirgends garantiert. Das hängt vom Betriebs- und Dateisystem ab. Man sollte die also sortieren wenn man die in einer vorhersehbaren Reihenfolge anzeigen möchte.

Ein `QGridLayout` bei dem Grundsätzlich nur die erste Spalte verwendet wird, kann durch ein `QHBoxLayout` ersetzt werden.

Den Unterstrich bei `exec_()` sollte man weglassen, die Methode wird es unter diesem Namen in PyQt6 nicht mehr geben.

Zwischenstand (ungetestet):

Code: Alles auswählen

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

from PyQt5 import QtCore
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QPushButton,
    QWidget,
)

RECIPES_PATH = Path("recipes")
BUTTON_STYLE_FILE_PATH = Path("style_buttons.css")


def clear_layout(layout):
    while True:
        item = layout.takeAt(0)
        if not item:
            break
        item.widget().deleteLater()


def replace_content_with_recipe(layout, recipe_text):
    clear_layout(layout)
    layout.addWidget(
        QPushButton(
            "Zurück",
            styleSheet=BUTTON_STYLE_FILE_PATH.read_text(encoding="utf-8"),
            clicked=partial(replace_content_with_recipe_choice, layout),
        )
    )
    layout.addWidget(QLabel(recipe_text))


def replace_content_with_recipe_choice(layout):
    clear_layout(layout)
    layout.addWidget(
        QLabel(
            "🍔Rezepte🍕",
            alignment=QtCore.Qt.AlignCenter,
            styleSheet="font-size: 30px;\nfont-family: Arial;",
        )
    )
    for recipe_path in sorted(RECIPES_PATH.glob("*.txt")):
        layout.addWidget(
            QPushButton(
                recipe_path.stem,
                styleSheet=BUTTON_STYLE_FILE_PATH.read_text(encoding="utf-8"),
                clicked=partial(
                    replace_content_with_recipe,
                    layout,
                    recipe_path.read_text(encoding="utf-8"),
                ),
            )
        )


def main():
    app = QApplication(
        sys.argv, applicationName="Rezepte", windowIcon=QIcon("icon.png")
    )
    app.setStyle("Fusion")

    window = QWidget(styleSheet=Path("style.css").read_text(encoding="utf-8"))
    layout = QHBoxLayout()
    replace_content_with_recipe_choice(layout)
    window.setLayout(layout)
    window.show()

    sys.exit(app.exec())


if __name__ == "__main__":
    main()
Das mit dem austauschen des Layout-Inhalts ist total ungewöhnlich, das würde man so nicht machen. Man erstellt GUI-Elemente in der Regel am Anfang komplett und ersetzt dann nur noch die angezeigten Inhalte. Wenn in einem Elternwidget zwei unterschiedliche Inhalte angezeigt werden sollen, dann verwendet man beispielsweise ein `QStackedWidget` und schaltet dort zwischen zwei Widgets mit den Inhalten um.

Das ist so auch ineffizient immer die gleichen Rezepte wieder und wieder einzulesen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten