Glade Gtk Treeview Anfänger

Programmierung für GNOME und GTK+, GUI-Erstellung mit Glade.
Antworten
Schnuggl
User
Beiträge: 4
Registriert: Montag 13. Januar 2020, 13:42

Montag 13. Januar 2020, 18:36

Hallo zusammen,
ich scheitere kläglich dabei mit einem Treeview umzugehen. . .

Es gelingt mir mit Glade ein Treeview mit verbundenem Liststore zu erstellen und die Signale im Python Code zu verwerten.
Aber das Konzept im Hintergrund ist mir unbegreiflich - ich habe einiges im Web dazu gelesen, aber entweder es ist zu schlecht erklärt, oder ich bin zu doof.
Was mir vermutlich weiterhilft, wäre eine Antwort auf folgende Fragen:
- Wie greife ich auf die im Treeview angezeigten Daten des Liststores zu? (Lesen und ändern)
- Wieso kann ich mit "li.append([1, 2, 3])" [zeile 21 in meinem Code] nicht eine "Zeile" anhängen?
Erst mit den folgenden Zeilen im Code [23 etc. ] werden von mir eigentlich unerwünschte Spalten hinzugefügt und dann erst erscheinen die Daten - natürlich an völlig falscher Stelle. . .
- Welche Einstellung in Glade brauche ich damit ich als Datentyp für die Spalten einen String bekomme?

Mir fehlt offensichtlich ein sehr grundlegendes Stück Verständnis, aber trotz langer Suche, finde ich keine mir verständliche Erklärung.
Vielleicht könnt Ihr mir etwas weiterhelfen?

Jeder Hinweis willkommen. . .

HG,

Der Python Code:

Code: Alles auswählen

#!/usr/bin/env python3
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class handler: # Signalverarbeitung für die Glade Signale
    def on_TVFenster_destroy(self, *args):
        Gtk.main_quit()
    def on_spalte1_clicked(self, ObjektSpalte):
        print("Spalte1 angeklickt")
        print(ObjektSpalte.get_title())
        tv.set_cursor(0) # path=0 ist die erste Zeile, 1 die zweite usw.
        tree_iter = li.get_iter(0)
        value = li.get_value(tree_iter, 0)
        print(value)
    def on_Treeview_cursor_changed(self, selection):
        print("TVListe Cursor changed")

class Start():
    def __init__(self):
        li.append([1, 2, 3])
        li.append([4, 5, 6])
        columns = ["Noch ńe Spalte", "5.", "6."]
        for i, column in enumerate(columns):              # for each column
            cell = Gtk.CellRendererText()                  # cellrenderer to render the text
            col = Gtk.TreeViewColumn(column, cell, text=i) # the column is created
            tv.append_column(col)            # and it is appended to the treeview

if __name__ == '__main__':
    builder = Gtk.Builder()
    builder.add_from_file("MiniTreeView.glade")
    builder.connect_signals(handler())
    window = builder.get_object("TVFenster")
    window.show_all()
    li = builder.get_object("Liststore")
    tv = builder.get_object("Treeview")
    Start()
    Gtk.main()
Die Glade Datei:

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkListStore" id="Liststore">
    <columns>
      <!-- column-name spalte1 -->
      <column type="gchar"/>
      <!-- column-name spalte2 -->
      <column type="gchar"/>
      <!-- column-name spalte3 -->
      <column type="gchar"/>
    </columns>
    <data>
      <row>
        <col id="0">0</col>
        <col id="1"/>
        <col id="2"/>
      </row>
    </data>
  </object>
  <object class="GtkWindow" id="TVFenster">
    <property name="can_focus">False</property>
    <signal name="destroy" handler="on_TVFenster_destroy" swapped="no"/>
    <child>
      <placeholder/>
    </child>
    <child>
      <object class="GtkTreeView" id="Treeview">
        <property name="visible">True</property>
        <property name="can_focus">True</property>
        <property name="model">Liststore</property>
        <signal name="cursor-changed" handler="on_Treeview_cursor_changed" swapped="no"/>
        <child internal-child="selection">
          <object class="GtkTreeSelection"/>
        </child>
        <child>
          <object class="GtkTreeViewColumn" id="spalte1">
            <property name="title" translatable="yes">column</property>
            <property name="clickable">True</property>
            <signal name="clicked" handler="on_spalte1_clicked" swapped="no"/>
          </object>
        </child>
        <child>
          <object class="GtkTreeViewColumn" id="spalte2">
            <property name="title" translatable="yes">Titel b</property>
            <property name="clickable">True</property>
          </object>
        </child>
        <child>
          <object class="GtkTreeViewColumn" id="spalte3">
            <property name="title" translatable="yes">column</property>
            <property name="clickable">True</property>
          </object>
        </child>
      </object>
    </child>
  </object>
</interface>
Benutzeravatar
__blackjack__
User
Beiträge: 4905
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Dienstag 14. Januar 2020, 11:19

@Schnuggl: Zeichenketten haben den Typ `gchararray` und in der GUI wird nichts angezeigt weil Du zwar die Spalten des Treeview in Glade definiert hast und gesagt hast welcher Liststore verwendet werden soll, dann aber keine Zuordnung gemacht hast was aus diesem Liststore in welcher Spalte und wie angezeigt werden soll. Also zum Beispiel das Liststore Spalte 1 in Treeview den Text für Spalte 1 liefern soll und so weiter. Der Treeview muss nicht jede Spalte des Liststore anzeigen und Spalten aus dem Liststore müssen auch keinen Text liefern, sondern können beispielsweise auch die Farbe oder Schriftattribute für eine Spalte im Treeview liefern. Wenn Du einfach die Spalten aus dem Liststore der Reihe nach als Text auf die Spalten des Treeview abbilden willst, musst Du in Glade jeder Treeview-Spalte einen `GtkCellRendererText` als Kind verpassen, mit dem entsprechenden Spaltenindex aus dem Liststore.

Dann zum Quelltext: Namen schreibt man in Python klein_mit_unterstrichen. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Also `Handler` statt `handler` und `on_tv_fenster_destroy` statt `on_TVFenster_destroy` und so weiter.

Bei `ObjektSpalte` macht das `Objekt` wenig Sinn — alles was man an einen Namen binden kann ist in Python ein Objekt. Das als Präfix in den Namen zu schreiben bringt keinen Mehrwert.

Bei GUI-Programmierung kommt man im Grunde nicht um objektorientierte Programmierung herum, also eigene Klassen. Das Programm enthält aber keine Klassen weil beide ”Klassen” semantisch überhaupt gar keine Klassen sind. Beide haben überhaupt keinen Zustand, Klassen sind ja aber dazu da um Zustand zu kapseln, und die `Start`-”Klasse” besteht nur aus einer `__init__()`, ist also noch nicht einmal ein Namensraum in den Funktionen gesteckt wurden, sondern selbst nur eine umständliche Art um *eine* Funktion zu schreiben.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Auf keinen Fall Variablen auf die dann auf magische Weise aus Funktionen oder Methoden zugegriffen wird, denn die sollten alles was sie ausser Konstanten benötigen als Argument(e) übergeben bekommen. Und als wenn das nicht so schon unübersichtlich genug wäre, haben die globalen Variablen auch noch so kryptische Namen wie `li` und `tv`.

Code: Alles auswählen

#!/usr/bin/env python3
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class App:
    def __init__(self):
        builder = Gtk.Builder()
        builder.add_from_file("MiniTreeView.glade")
        builder.connect_signals(self)

        window = builder.get_object("TVFenster")
        window.show_all()
        self.list_store = builder.get_object("Liststore")
        self.tree_view = builder.get_object("Treeview")

        self.list_store.append(["1", "2", "3"])
        self.list_store.append(["4", "5", "6"])

    @staticmethod
    def on_tv_fenster_destroy(_widget):
        Gtk.main_quit()

    def on_spalte1_clicked(self, column):
        print("Spalte1 angeklickt")
        print(column.get_title())
        # path=0 ist die erste Zeile, 1 die zweite usw.
        self.tree_view.set_cursor(0)
        tree_iter = self.list_store.get_iter(0)
        value = self.list_store.get_value(tree_iter, 0)
        print(value)

    @staticmethod
    def on_treeview_cursor_changed(_tree_view):
        print("TVListe Cursor changed")


def main():
    _app = App()
    Gtk.main()


if __name__ == "__main__":
    main()

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkListStore" id="Liststore">
    <columns>
      <!-- column-name spalte1 -->
      <column type="gchararray"/>
      <!-- column-name spalte2 -->
      <column type="gchararray"/>
      <!-- column-name spalte3 -->
      <column type="gchararray"/>
    </columns>
    <data>
      <row>
        <col id="0" translatable="yes">spam</col>
        <col id="1" translatable="yes">abc</col>
        <col id="2" translatable="yes">xyz</col>
      </row>
    </data>
  </object>
  <object class="GtkWindow" id="TVFenster">
    <property name="can_focus">False</property>
    <signal name="destroy" handler="on_tv_fenster_destroy" swapped="no"/>
    <child>
      <placeholder/>
    </child>
    <child>
      <object class="GtkTreeView" id="Treeview">
        <property name="visible">True</property>
        <property name="can_focus">True</property>
        <property name="model">Liststore</property>
        <signal name="cursor-changed" handler="on_treeview_cursor_changed" swapped="no"/>
        <child internal-child="selection">
          <object class="GtkTreeSelection"/>
        </child>
        <child>
          <object class="GtkTreeViewColumn" id="spalte1">
            <property name="title" translatable="yes">column</property>
            <property name="clickable">True</property>
            <signal name="clicked" handler="on_spalte1_clicked" swapped="no"/>
            <child>
              <object class="GtkCellRendererText"/>
              <attributes>
                <attribute name="text">0</attribute>
              </attributes>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkTreeViewColumn" id="spalte2">
            <property name="title" translatable="yes">Titel b</property>
            <property name="clickable">True</property>
            <child>
              <object class="GtkCellRendererText"/>
              <attributes>
                <attribute name="text">1</attribute>
              </attributes>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkTreeViewColumn" id="spalte3">
            <property name="title" translatable="yes">column</property>
            <property name="clickable">True</property>
            <child>
              <object class="GtkCellRendererText"/>
              <attributes>
                <attribute name="text">2</attribute>
              </attributes>
            </child>
          </object>
        </child>
      </object>
    </child>
  </object>
</interface>
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
Schnuggl
User
Beiträge: 4
Registriert: Montag 13. Januar 2020, 13:42

Dienstag 14. Januar 2020, 23:24

@blackjack: Erst mal herzlichen Dank, für die schnelle und unerwartet ausführliche Antwort!

Eine meiner Fragen ist noch offen (oder ich hab' die Antwort noch nicht ausreichend verstanden):
- Wie kann ich die im Treeview angezeigten Daten des Liststores ändern?
ich vermute so was wie:
self.list_store.set(self.list_store, tree_iter, "spalte1", "Neuer Wert1","spalte2", "Neuer Wert2","spalte3", "Neuer Wert3",-1)
liefert aber diesen Fehler:
TypeError: Argument list must be in the form of (column, value, ...), ((columns,...), (values, ...)) or {column: value}. No -1 termination is needed.
Wo hänge ich?


Und noch zu Deinen Anmerkungen zu meinem Quelltext:
Werde versuchen mich in Zukunft an die Konventionen zu halten.

ObjektSpalte hatte für mich den Sinn, mich daran zu erinnern, dass es sich hier nicht um eine Variable (z.b. index der Spalten als integer),
sondern eben um ein Objekt handelt. Solche Namensgebungen helfen mir bei meinem Lernprozess.
Ich will Deine Kritik damit nicht zurückweisen. Du hast natürlich recht damit. Möchte nur erklären, wie ich auf solche verrückten Ideen komme.

Zum Thema objektorientierte Programmierung, Klassen, Semantik, Zustände, kapseln, Namensräume, etc.
Ich könnte sicher eine gute Einführung in die objektorientierte Programmierung gebrauchen - kannst Du was im Web empfehlen?

Auch meine kryptische Namensgebung hatte aus meiner Sicht Gründe. Wenn der Editor oder die Programmiersprache keine besonderen Formalien erzwingt, neige ich dazu es so zu machen, wie es in der Situation gerade hilfreich für mich erscheint.
Insofern bin ich da sicher etwas zu entspannt - tut mir Leid wenn das nervt. Ist nicht meine Absicht.

Falls sich noch ein paar Neulinge hierher verirren und ähnliche Probleme wie ich haben -
hier noch 2 Anmerkungen (weil ich doch etwas Zeit gebraucht habe, um die Antwort umsetzen zu können. . . ):
- in Glade jeder Treeview-Spalte einen `GtkCellRendererText` als Kind verpassen geht so:
Rechtsklick auf das Treeview - edit, dann Rechtsklick auf die Spalte und "Kindeintrag Text hinzufügen"
- Den Spaltenindex aus dem Liststore wählt man im Register Allgemein.
Das Dropdown- Menü mit den Auswahlmöglichkeiten kann versteckt sein -
die rechte Spalte in Glade größer ziehen hilft, wenn man (wie ich) den Scrollbalken unten übersieht. . .

Vielleicht hilft's ja mal jemandem. . .
Benutzeravatar
__blackjack__
User
Beiträge: 4905
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Mittwoch 15. Januar 2020, 11:17

@Schnuggl: Das `set()` braucht den Liststore auf dem es aufgerufen wird nicht als erstes Argument, denn es wird ja auf dem Liststore aufgerufen. Wäre komisch wenn man da noch den Liststore angeben müsste der durch den Aufruf verändert werden soll.

Die -1 ist nicht nötig beziehungsweise sogar falsch, weil die als weitere Spaltennummer interpretiert wird. Die ist in C nötig damit die Funktion feststellen kann wann die dynamische Argumentliste zuende ist, in Python kann man dagegen die Anzahl der dynamischen Argumente in einer Funktion aber einfach von der Liste abfragen.

Die Spalten sind Indexwerte der Spalten und nicht die Namen. Also eine Möglichkeit, die am nächsten am C-Aufruf ist:

Code: Alles auswählen

        self.list_store.set(
            tree_iter, 0, "Neuer Wert1", 1, "Neuer Wert2", 2, "Neuer Wert3"
        )
Die Fehlermeldung die Du bekommst listet noch zwei Alternativen auf: Spaltenindizes und Werte als zwei Sequenzen:

Code: Alles auswählen

        self.list_store.set(
            tree_iter, [0, 1, 2], ["Neuer Wert1", "Neuer Wert2", "Neuer Wert3"]
        )
Und als Wörterbuch:

Code: Alles auswählen

        self.list_store.set(
            tree_iter, {0: "Neuer Wert1", 1: "Neuer Wert2", 2: "Neuer Wert3"}
        )
Re `ObjectSpalte`: Da Indexwerte in Python nicht so wirklich oft sinnvoll sind, sollte man eher bei `spalte` davon ausgehen, dass das kein Indexwert ist sondern ein komplexeres Objekt das eine Spalte modelliert, und einen Spaltenindex dann tatsächlich `spaltenindex` nennen, damit das klar wird. Hier kann es sein das Gtk einen dazu zwingt mit Indexwerten zu hantieren, weil man das in C machen muss, aber wenn man in normalem Python-Code viel mit Indexwerten macht, macht man in der Regel etwas falsch.

Namensgebung macht man doch nicht für die Programmiersprache oder den Editor sondern für die Menschen die das lesen. Dem Rechner ist im allgemeinen egal ob man alle Namen so kurz wie möglich wählt und beispielsweise alles mit a, b, c, …, X, Y, Z durchbenennt. Menschen die das lesen schliesst den Autor selbst mit ein, der ja auch in ein paar Wochen noch leicht verstehen sollte wofür die Namen stehen. Und es gibt da ja auch allgemeine ”Formalien” die relativ unabhängig von der verwendeten Programmiersprache sind. Zum Beispiel je grösser der Sichtbarkeitsbereich eines Namens ist, desto besser verständlich sollte der Name sein. `li` und `tv` sind global gewesen und nicht nur beispielsweise auf einen Ausdruck beschränkt. Als lokaler Name in einem ``lambda``-Ausdruck oder in einer „comprehension” oder einem Generatorausdruck können zweibuchstabige Namen manchmal ausreichen.
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
Schnuggl
User
Beiträge: 4
Registriert: Montag 13. Januar 2020, 13:42

Mittwoch 15. Januar 2020, 18:08

@blackjack: Super! Erneut vielen Dank!

Ich stimme Dir mit der Namensgebung völlig zu.
Ich wollte zu meiner Ehrenrettung ja nur darauf hinweisen, dass das kein Programm für mich darstellt, das ich jemals wieder anrühre.
Es ging mir nur darum ein TreeView nutzen zu können und zu checken, ob es für mein nächstes Projekt Sinn macht sich näher mit Python zu beschäftigen.
Das hab' ich jetzt beschlossen - zu Deinem Leidwesen kann es also passieren, dass ich mal wieder was poste. . . :D
Antworten