Element aus dem Layout entfernen | Python GUI mit Kivy

In den vorangegangenen Teilen der Serie über Kivy haben wir Aktien als Widgets zu unserem Layout hinzugefügt.
Aber was, wenn wir eine Aktie wieder loswerden wollen?
In dem Beitrag siehst du, wie schnell du mit Kivy ein Element aus dem Layout entfernen kannst.

Unsicher mit Git?
Lade jetzt dein Git CheatSheet herunter!

Kivy Serie

Einen Button zum Löschen der Zeile hatten wir ja bereits zugefügt.
Allerdings ist die einzige Aktion aktuell eine Ausgabe “Aktien entfernen”.
Damit sollte sichergestellt werden, dass der Button überhaupt funktioniert.

def remove_ticker_row(self, *args):
    print('Aktien entfernen')

Jetzt wollen wir dem Button auch Funktion geben.

Das Element im Layout finden

Zunächst müssen wir natürlich das Element finden, dass wir aus dem Layout entfernen wollen.
Wir haben an die Funktionen bisher immer ein *args übergeben.
Das war dafür da, unnötige Informationen abzufangen, die Kivy und beim Aufruf mit übergibt.

Genau jetzt brauchen wir diese Informationen.
*args ist einfach eine Liste für alle Werte, die Kivy uns übergibt.
Gleich der erste Wert – und der Einzige in dem Fall – ist die Komponente, die die Funktion ausgelöst hat.
Das ist dann der Button, der gedrückt wurde.
Unser löschen Button.

Nehmen wir uns den also zuerst einmal aus der Liste heraus:

def remove_ticker_row(self, *args):
    delete_button = args[0]
    print(delete_button)

Das sollte zu einer Ausgabe wie dieser führen:

<kivy.uix.button.Button object at 0x135771bd0>

Position bestimmen

Jetzt reicht es natürlich nicht, den Button an der Hand zu haben.
Wir brauchen alle Komponenten in der Zeile.
Dafür gibt es zwei Möglichkeiten.

  1. Wir können unseren Code umbauen
    Dafür müssten wir das GridLayout zu einer Spalte ändern, oder ganz entfernen.
    Dann können wir alle Komponenten einer Zeile in einen Container, wie ein BoxLayout legen.
    Abschließend kann dann über das Attribut .parent an dem Button das BoxLayout ausgewählt und aus dem Layout entfernt werden.
  2. Alternativ bestimmen wir eine Zeile einfach anhand der Position im Layout.
    Damit können wir dann jedes Element aus dem Layout entfernen, dass sich in derselben Zeile befindet.

Code umbauen klingt mir jetzt etwas zu aufwändig.
Es geht ja nur darum, schnell ein Element aus dem Layout entfernen zu können.

Also machen wir das doch einfach über die Position.
Positionen werden in Kivy über Koordinaten (x und y) bestimmt.
So hat auch unser Button eine x und eine y Koordinate.
Freundlicherweise liefert uns Kivy das direkt als Parameter am Button.
So können wir über delete_button.y direkt auf die y-Koordinate zugreifen

def remove_ticker_row(self, *args):
    delete_button = args[0]
    button_y = delete_button.y

Alle Elemente derselben Zeile finden

Nachdem wir jetzt die y-Koordinate von unserem Button haben, müssen wir lediglich durch das Layout gehen und mit den anderen Komponenten abgleichen.

Ich hatte ja schon gesagt, dass es an einem Widget das Attribut .parent gibt.
Damit gelangen wir von dem Button an das umgebende Element.
Das ist bei uns direkt das GridLayout stock_list, in dem sich alle Elemente befinden.

def remove_ticker_row(self, *args):
    delete_button = args[0]
    button_y = delete_button.y
    stock_list_widget = delete_button.parent
    print(stock_list_widget)
<kivy.uix.gridlayout.GridLayout object at 0x169300200>

Wenn es ein parent gibt, ist es naheliegend, dass es auch ein children Attribut gibt.
Und genau so ist es auch.
Über stock_list_widget.children bekommen wir alle Elemente in dem GridLayout.
Also alle Elemente, aller Zeilen.

def remove_ticker_row(self, *args):
    delete_button = args[0]
    button_y = delete_button.y
    stock_list_widget = delete_button.parent
    stock_list_widget_children = stock_list_widget.children
    print(stock_list_widget_children)
[<kivy.uix.button.Button object at 0x17f9784a0>, <kivy.uix.label.Label object at 0x17f9da180>, <kivy.uix.label.Label object at 0x17f93e180>, <kivy.uix.label.Label object at 0x16fe5f530>, <kivy.uix.button.Button object at 0x17f946dc0>, <kivy.uix.label.Label object at 0x17f944b30>, <kivy.uix.label.Label object at 0x17f969c40>, <kivy.uix.label.Label object at 0x17f96bed0>]

Nachdem das erledigt ist, brauchen wir alle Elemente, die auf derselben Höhe – also in derselben Zeile – platziert sind.
Das kann einfach mit einer kurzen for-Schleife erledigt werden:

    def remove_ticker_row(self, *args):
        delete_button = args[0]
        button_y = delete_button.y
        stock_list_widget = delete_button.parent
        stock_list_widget_children = stock_list_widget.children

        row = []
        for element in stock_list_widget_children:
            if element.y == button_y:
                row.append(element)

        print(row)
[<kivy.uix.button.Button object at 0x1759cf680>, <kivy.uix.label.Label object at 0x1759cd3f0>, <kivy.uix.label.Label object at 0x175a960a0>, <kivy.uix.label.Label object at 0x16d70b610>]

Damit haben wir genau die 4 Elemente, die alle in einer Zeile liegen.

Element aus dem Layout entfernen

Wenn wir jetzt ein Element aus dem Layout entfernen möchten, bietet Kivy uns mit der Funktion remove_widget() an einem Layout direkt die Möglichkeit dazu.
Über self.stock_list.remove_widget(delete_button) könnten wir jetzt also den Button aus dem GridLayout entfernen.

Jetzt haben wir allerdings eine ganze Zeile, die wir entfernen möchten.
Dafür können wir auch wieder eine for-Schleife bemühen:

def remove_ticker_row(self, *args):
    delete_button = args[0]
    button_y = delete_button.y
    stock_list_widget = delete_button.parent
    stock_list_widget_children = stock_list_widget.children

    row = []
    for element in stock_list_widget_children:
        if element.y == button_y:
            row.append(element)

    for element in row:
        self.stock_list.remove_widget(element)

Perfekt!
So einfach können wir ein Element aus dem Layout entfernen!

Aber der Code ist natürlich nicht schön.
Das räumen wir jetzt noch ein wenig auf.

Refactoring

Als ersten Schritt wollen wir nicht so viele einzelne Variablen haben.
Vor allem nicht, wen die Werte einfach nur aus einem Parameter kommen.
Lösen wir also die Variablen einfach mal auf.
Zuerst delete_button:

def remove_ticker_row(self, *args):
    button_y = args[0].y
    stock_list_widget = args[0].parent
    stock_list_widget_children = stock_list_widget.children

    row = []
    for element in stock_list_widget_children:
        if element.y == button_y:
            row.append(element)

    for element in row:
        self.stock_list.remove_widget(element)

Als Nächstes ist button_y an der Reihe:

def remove_ticker_row(self, *args):
    stock_list_widget = args[0].parent
    stock_list_widget_children = stock_list_widget.children

    row = []
    for element in stock_list_widget_children:
        if element.y == args[0].y:
            row.append(element)

    for element in row:
        self.stock_list.remove_widget(element)

Dann kommt stock_list_widget dran:

def remove_ticker_row(self, *args):
    stock_list_widget_children = args[0].parent.children

    row = []
    for element in stock_list_widget_children:
        if element.y == args[0].y:
            row.append(element)

    for element in row:
        self.stock_list.remove_widget(element)

Und jetzt bleibt uns noch stock_list_widget_children:

def remove_ticker_row(self, *args):
    row = []
    for element in args[0].parent.children:
        if element.y == args[0].y:
            row.append(element)

    for element in row:
        self.stock_list.remove_widget(element)

Wenn wir uns ansehen, was jetzt noch übrig bleibt, sollte auffallen, dass wir die Variable row nur befüllen, um sie in der nächsten Schleife wieder auszulesen.
Sonst passiert nichts weiter.
Das können wir uns also auch sparen und direkt alles in einer for-Schleife erledigen.

Wenn ich allerdings das Layout, dass ich gerade durchsuche, auch gleichzeitig bearbeite – löschen ist eine Form von bearbeiten – kommt es zu einem Fehlverhalten.
Es werden nur zwei der 4 nötigen Elemente entfernt.
Vermutlich liegt es daran, dass die Liste kürzer wird, während wir sie durchlaufen.
Damit kommt der Index durcheinander und es wird nicht mehr alles erwischt.

Was wir aber machen können, ist, die erste for-Schleife in einer List-Comprehension abzuarbeiten:

def remove_ticker_row(self, *args):
    row = [element for element in args[0].parent.children if element.y == args[0].y]

    for element in row:
        self.stock_list.remove_widget(element)

Und schon sieht der Code doch gleich viel besser und viel übersichtlicher aus!

Zusammenfassung

Beim Klick auf den Button wird uns die gesamte Button-Komponente an den Callback – also an die Funktion, die der Button auslöst – mit übergeben.
Damit haben wir Zugriff auf die Position des Buttons und auch auf das Parent-Element – also unser GridLayout.

Mit der y-Koordinate des Buttons können wir jetzt durch die children des GridLayouts iterieren und einfach alle löschen, die in derselben Zeile liegen.

Wenn du Fragen hast, schau gerne im Discord vorbei oder hinterlass mir hier einen Kommentar.

Wie kannst du am schnellsten Summen... x
Wie kannst du am schnellsten Summen bilden? Python Built-Ins | #Shorts

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht.

Scroll to Top
ANFORDERN
Anfordern
Bereit zum Ausdrucken | Mit Erklärungen
IMMER ZUR HAND
ANFORDERN
Anfordern
Bereit zum Ausdrucken | Mit Erklärungen
IMMER ZUR HAND
Trag dich schnell ein und bekomme Zugang zum Download!
Gib mir den Download!