# Treeview με ListStore (JavaScript)

Taryn Fox
jewelfox@fursona.net 2012
Ένα γραφικό συστατικό που εμφανίζει μια ξεχωριστή λίστα στοιχείων

# Treeview με ListStore

Μια TreeView μοιάζει με ένα παράθυρο στα περιεχόμενα είτε μιας ListStore είτε μιας TreeStore. Μια ListStore μοιάζει με ένα υπολογιστικό φύλλο: μια "επίπεδη", διδιάστατη λίστα πραγμάτων διαιρεμένη σε γραμμές και στήλες. Μια TreeStore, στο μεταξύ, μπορεί να διακλαδωθεί σε διαφορετικές κατευθύνσεις όπως κάνει ένα δένδρο. Σε αυτό το παράδειγμα, δημιουργήσαμε μια TreeView που εμφανίζει τα περιεχόμενα μιας ListStore με (εικονικά) ονόματα και αριθμούς τηλεφώνου σε αυτό, και ρυθμίστηκε έτσι ώστε η ετικέτα στο τέλος του παραθύρου να εμφανίζει περισσότερες πληροφορίες σχετικά με οποιοδήποτε όνομα πατάτε.

Η TreeView δεν είναι απλά ένα μονό γραφικό συστατικό, αλλά περιέχει έναν αριθμό μικρότερων γραφικών συστατικών:

Τα γραφικά συστατικά TreeViewColumn εμφανίζουν κάθε (κάθετη) στήλη πληροφορίας από τη ListStore. Καθένα έχει έναν τίτλο που μπορεί να προβληθεί στην κορυφή της στήλης, όπως στο στιγμιότυπο.

Τα γραφικά συστατικά CellRenderer "πακετάρονται" σε κάθε TreeViewColumn και περιέχουν τις οδηγίες πώς να εμφανίσουν κάθε ξεχωριστό "κελί" ή στοιχείο από τη ListStore. Υπάρχουν πολλοί διαφορετικοί τύποι, συμπεριλαμβανόμενου του CellRendererText που χρησιμοποιείται εδώ και του CellRendererPixbuf, που εμφανίζει μια εικόνα "ενδιάμεση μνήμη εικονοστοιχείου").

Τελικά, πρόκειται να χρησιμοποιήσουμε ένα αντικείμενο που λέγεται TreeIter, που δεν είναι γραφικό συστατικό τόσο πολύ όσο ένας αόρατος δρομέας που δείχνει σε μια (οριζόντια) γραμμή στη ListStore. Οποτεδήποτε πατάτε σε ένα όνομα στον τηλεφωνικό κατάλογο, για παράδειγμα, δημιουργούμε ένα TreeIter που δείχνει τη γραμμή που επιλέχτηκε και έπειτα χρησιμοποιήστε το για να πείτε στη ListStore ποιας εγγραφής θέλουμε η ετικέτα να εμφανίσει περισσότερες πληροφορίες σχετικά.

Η TreeView είναι προφανώς το πιο περίπλοκο γραφικό συστατικό Gtk, λόγω των πολλών μερών που έχει και του τρόπου συνεργασίας τους. Δώστε στον εαυτό σας τον χρόνο να μάθετε πώς δουλεύει και πειραματιστείτε μαζί του, ή δοκιμάστε πρώτα κάτι ευκολότερο εάν έχετε πρόβλημα.

# Βιβλιοθήκες για εισαγωγή

#!/usr/bin/gjs

const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Lang = imports.lang; const Pango = imports.gi.Pango;

Αυτές είναι οι βιβλιοθήκες που χρειαζόμαστε να εισάγουμε αυτήν την εφαρμογή για να εκτελεστεί. Να θυμόσαστε ότι η γραμμή που λέει στο GNOME ότι χρησιμοποιούμε Gjs χρειάζεται πάντοτε να πάει στην αρχή.

# Δημιουργία του παραθύρου εφαρμογής

const TreeViewExample = new Lang.Class({ Name: 'TreeView Example with Simple ListStore',

// Create the application itself _init: function() { this.application = new Gtk.Application({ application_id: 'org.example.jstreeviewsimpleliststore' });

// Connect 'activate' and 'startup' signals to the callback functions this.application.connect('activate', Lang.bind(this, this._onActivate)); this.application.connect('startup', Lang.bind(this, this._onStartup)); },

// Callback function for 'activate' signal presents window when active _onActivate: function() { this._window.present(); },

// Callback function for 'startup' signal builds the UI _onStartup: function() { this._buildUI (); },

Όλος ο κώδικας για αυτό το παράδειγμα πηγαίνει στην κλάση TreeViewExample. Ο παραπάνω κώδικας δημιουργεί μια Gtk.Application για να μπουν μέσα τα γραφικά συστατικά μας και τα παράθυρα.

// Build the application's UI _buildUI: function() {

// Create the application window this._window = new Gtk.ApplicationWindow({ application: this.application, window_position: Gtk.WindowPosition.CENTER, default_height: 250, default_width: 100, border_width: 20, title: "My Phone Book"});

Η συνάρτηση _buildUI είναι εκεί που βάζουμε όλον τον κώδικα για να δημιουργήσουμε τη διεπαφή χρήστη της εφαρμογής. Το πρώτο βήμα δημιουργεί ένα νέο Gtk.ApplicationWindow για να βάλουμε μέσα τα γραφικά συστατικά μας.

# Δημιουργία της ListStore

// Create the underlying liststore for the phonebook this._listStore = new Gtk.ListStore (); this._listStore.set_column_types ([ GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING]);

Δημιουργούμε πρώτα τη ListStore όπως θα κάναμε με κάθε γραφικό συστατικό. Έπειτα καλούμε τη μέθοδο της set_column_types και την περνάμε σε έναν πίνακα τύπου δεδομένων GObject. (Θα μπορούσαμε να βάλουμε όλους τους τύπους σε μια γραμμή, αλλά εδώ τα διαιρούμε για διευκόλυνση της ανάγνωσης.)

Οι τύποι δεδομένων GObject που μπορείτε να χρησιμοποιήσετε περιλαμβάνουν:

GObject.TYPE_BOOLEAN -- αληθές ή ψευδές

GObject.TYPE_FLOAT -- Ένας αριθμός κινητής υποδιαστολής (ένας με δεκαδική υποδιαστολή)

GObject.TYPE_STRING -- Μια συμβολοσειρά γραμμάτων και αριθμών

gtk.gdk.Pixbuf -- Μια εικόνα

Σε αυτήν την περίπτωση, κάνουμε μια ListStore των τεσσάρων στηλών, καθεμιά περιέχει τιμές συμβολοσειράς.

Χρειάζεται να βάλετε τη γραμμή const GObject = imports.gi.GObject; στην αρχή του κώδικα της εφαρμογή σας, όπως κάναμε σε αυτό το παράδειγμα, εάν θέλετε να μπορείτε να χρησιμοποιήσετε τύπους GObject.

// Data to go in the phonebook this.phonebook = let phonebook = [{ name: "Jurg", surname: "Billeter", phone: "555-0123", description: "A friendly person."}, { name: "Johannes", surname: "Schmid", phone: "555-1234", description: "Easy phone number to remember."}, { name: "Julita", surname: "Inca", phone: "555-2345", description: "Another friendly person."}, { name: "Javier", surname: "Jardon", phone: "555-3456", description: "Bring fish for his penguins."}, { name: "Jason", surname: "Clinton", phone: "555-4567", description: "His cake's not a lie."}, { name: "Random J.", surname: "Hacker", phone: "555-5678", description: "Very random!"}];

Εδώ έχουμε την πληροφορία μετάβασης στη ListStore. Είναι ένας πίνακας αντικειμένων, που καθένα αντιστοιχεί σε μια μοναδική εγγραφή στον τηλεφωνικό κατάλογο.

Σημειώστε ότι η TreeView στο στιγμιότυπο δεν εμφανίζει πράγματι τα δεδομένα από τις ιδιότητες "description". Στη θέση της, αυτή η πληροφορία εμφανίζεται στην ετικέτα από κάτω της, για κάθε γραμμή που πατάτε. Αυτό συμβαίνει επειδή η TreeView και η ListStore είναι δύο ξεχωριστά πράγματα και μια TreeView μπορεί να εμφανίσει όλο ή μέρος της ListStore και προβάλλει τι είναι σε αυτή με διαφορετικούς τρόπους. Μπορείτε ακόμα να έχετε πολλαπλά γραφικά συστατικά που εμφανίζουν πράγματα από την ίδια ListStore, όπως την ετικέτα στο παράδειγμά μας ή ακόμα μια δεύτερη TreeView.

for (i = 0; i < phonebook.length; i++ ) { let contact = phonebook [i]; this._listStore.set (this._listStore.append(), [0, 1, 2, 3], [contact.name, contact.surname, contact.phone, contact.description]); }

Αυτός ο βρόχος for βάζει τις συμβολοσειρές από τον τηλεφωνικό κατάλογό μας στη ListStore μας σε σειρά. Για να περάσουμε την μέθοδο ορισμού της ListStore στην επανάληψη που δείχνει στη σωστή γραμμή, χρειάζεται ένας πίνακας που λέει ποιες στήλες θέλουμε να ορίσουμε και ένας πίνακας που περιέχει τα δεδομένα που θέλουμε να βάλουμε σε αυτές τις στήλες.

Μια μέθοδος append της ListStore προσθέτει μια οριζόντια γραμμή σε αυτή (ξεκινά χωρίς καμία) και επιστρέφει μια TreeIter που δείχνει σε αυτήν τη γραμμή όπως ένας δρομέας. Έτσι περνώντας το this._listStore.append() στη ListStore ως μια ιδιότητα, δημιουργούμε μια νέα γραμμή και λέμε στη μέθοδο set ποια γραμμή να ορίσει τα δεδομένα ταυτόχρονα.

# Δημιουργία της TreeView

// Create the treeview this._treeView = new Gtk.TreeView ({ expand: true, model: this._listStore });

Εδώ δημιουργούμε ένα βασικό γραφικό συστατικό TreeView, που επεκτείνεται οριζόντια και κάθετα για να χρησιμοποιήσει όσο χώρο χρειάζεται. Το ορίζουμε να χρησιμοποιήσει τη ListStore που δημιουργήσαμε ως το "model" του, ή το πράγμα που θα μας δείξει το υλικό.

// Create the columns for the address book let firstName = new Gtk.TreeViewColumn ({ title: "First Name" }); let lastName = new Gtk.TreeViewColumn ({ title: "Last Name" }); let phone = new Gtk.TreeViewColumn ({ title: "Phone Number" });

Τώρα δημιουργούμε καθεμιά από τις κάθετες TreeViewColumns που βλέπουμε στην TreeView. Ο τίτλος για καθεμιά πηγαίνει στην κορυφή, όπως μπορείτε να δείτε στο στιγμιότυπο.

// Create a cell renderer for when bold text is needed let bold = new Gtk.CellRendererText ({ weight: Pango.Weight.BOLD });

// Create a cell renderer for normal text let normal = new Gtk.CellRendererText ();

// Pack the cell renderers into the columns firstName.pack_start (bold, true); lastName.pack_start (normal, true); phone.pack_start (normal, true);

Εδώ δημιουργούμε τους CellRenderers που θα χρησιμοποιήσουμε για την εμφάνιση του κειμένου από τη ListStore μας και τους πακετάρουμε στις TreeViewColumns. Κάθε CellRendererText χρησιμοποιείται για όλες τις καταχωρίσεις σε αυτήν τη στήλη. Το κανονικό μας CellRendererText απλά δημιουργεί απλό κείμενο, ενώ το έντονο κείμενο χρησιμοποιεί κείμενο βαρύτερου πάχους. Το βάζουμε στη στήλη πρώτου ονόματος και λέμε στα άλλα δύο να χρησιμοποιήσουμε αντίγραφα του κανονικού. Το "αληθές" χρησιμοποιείται ως η δεύτερη παράμετρος για τη μέθοδο pack_start που του λέει να επεκτείνει τα κελιά όταν είναι δυνατό, αντί να τα κρατήσει συμπαγή.

Εδώ είναι μια λίστα των άλλων ιδιοτήτων του κειμένου που μπορείτε να δείτε. Για να χρησιμοποιήσετε αυτές τις σταθερές Pango, βεβαιωθείτε ότι βάλατε τη γραμμή const Pango = imports.gi.Pango; στην αρχή του κώδικα σας όπως κάναμε.

firstName.add_attribute (bold, "text", 0); lastName.add_attribute (normal, "text", 1); phone.add_attribute (normal, "text", 2);

// Insert the columns into the treeview this._treeView.insert_column (firstName, 0); this._treeView.insert_column (lastName, 1); this._treeView.insert_column (phone, 2);

Τώρα που έχουμε βάλει τους CellRenderers στις TreeViewColumns, χρησιμοποιούμε τη μέθοδο add_attribute για να πει σε κάθε στήλη να σύρει το κείμενο από το πρότυπό μας TreeView που ορίστηκε για χρήση· σε αυτήν την περίπτωση, τη ListStore με τον τηλεφωνικό κατάλογο.

Η πρώτη παράμετρος είναι όποια CellRenderer πρόκειται να χρησιμοποιήσουμε για απόδοση του τι σύρουμε.

Η δεύτερη παράμετρος είναι τι είδος πληροφορίας πρόκειται να σύρουμε. Σε αυτήν την περίπτωση, την αφήνουμε να μάθει ότι αποδίδουμε κείμενο.

Η τρίτη παράμετρος είναι σε ποια στήλη της ListStore σύρουμε αυτήν την πληροφορία.

Αφού το ρυθμίσουμε, χρησιμοποιούμε τη μέθοδο insert_column του TreeView για να βάλουμε τις TreeViewColumns μας μέσα του σε σειρά. Η TreeView μας είναι τώρα πλήρης.

Κανονικά, θα μπορούσατε να θελήσετε να χρησιμοποιήσετε έναν βρόχο για αρχικοποίηση την TreeView σας, αλλά σε αυτό το παράδειγμα εξηγούμε λεπτομερώς τα πράγματα βήμα-βήμα για διευκόλυνση της κατανόησης.

# Δημιουργία της υπόλοιπης διεπαφής χρήστη

// Create the label that shows details for the name you select this._label = new Gtk.Label ({ label: "" });

// Get which item is selected this.selection = this._treeView.get_selection();

// When something new is selected, call _on_changed this.selection.connect ('changed', Lang.bind (this, this._onSelectionChanged));

Η μέθοδος get_selection της TreeView επιστρέφει ένα αντικείμενο που λέγεται TreeSelection. Μια TreeSelection είναι όπως μια TreeIter στο ότι είναι βασικά ένας δρομέας που δείχνει σε μια συγκεκριμένη γραμμή, εκτός από το ότι αυτό που δείχνει είναι αυτό που είναι ορατά επισημασμένο καθώς επιλέχτηκε.

Αφού πάρουμε την TreeSelection που πηγαίνει με την TreeView μας, της ζητάμε να μας πει όταν αλλάζει σε ποια γραμμή δείχνει. Το κάνουμε συνδέοντας τι σήμα της changed στη συνάρτηση _onSelectionChanged που γράψαμε. Αυτή η συνάρτηση αλλάζει το εμφανιζόμενο κείμενο με την ετικέτα που μόλις κάναμε.

// Create a grid to organize everything in this._grid = new Gtk.Grid;

// Attach the treeview and label to the grid this._grid.attach (this._treeView, 0, 0, 1, 1); this._grid.attach (this._label, 0, 1, 1, 1);

// Add the grid to the window this._window.add (this._grid);

// Show the window and all child widgets this._window.show_all(); },

Αφού το έχουμε βγάλει εκτός, δημιουργούμε ένα πλέγμα για να τα βάλουμε όλα μέσα, έπειτα το προσθέτουμε στο παράθυρό μας και λέμε στο παράθυρο να εμφανίσει τον εαυτόν του και τα περιεχόμενα του.

# Η συνάρτηση που χειρίζεται μια αλλαγμένη επιλογή

_onSelectionChanged: function () {

// Grab a treeiter pointing to the current selection let [ isSelected, model, iter ] = this.selection.get_selected();

// Set the label to read off the values stored in the current selection this._label.set_label ("\n" + this._listStore.get_value (iter, 0) + " " + this._listStore.get_value (iter, 1) + " " + this._listStore.get_value (iter, 2) + "\n" + this._listStore.get_value (iter, 3));

}

});

Η γραμμή του κώδικα με την δήλωση let είναι λίγο μπερδεμένη, αλλά παρόλα αυτά είναι ο άριστος τρόπος να κάνετε την TreeIter να δείχνει στην ίδια γραμμή όπως η TreeSelection μας. Πρέπει να δημιουργήσουμε λίγες άλλες αναφορές αντικειμένου, αλλά η iter είναι το μόνο που χρειαζόμαστε.

Αφού το τελειώσουμε, καλούμε τη συνάρτηση της ετικέτας set_label και χρησιμοποιούμε τη συνάρτηση της ListStore get_value λίγες φορές για να συμπληρώσουμε τα δεδομένα που θέλουμε να τα εισάγουμε. Οι παράμετροι της είναι μια TreeIter που δείχνει στη γραμμή από όπου θέλουμε να πάρουμε δεδομένα και τη στήλη.

Εδώ, θέλουμε να πάρουμε δεδομένα και από τις τέσσερις στήλες, συμπεριλαμβάνοντας την "κρυφή" που δεν είναι μέρος της TreeView. Έτσι, μπορούμε να χρησιμοποιήσουμε την ετικέτα μας για να εμφανίσουμε συμβολοσειρές που είναι υπερβολικά μεγάλες για προσαρμογή στη TreeView και που δεν χρειαζόμαστε να δούμε με μια ματιά.

// Run the application let app = new TreeViewExample (); app.application.run (ARGV);

Τελικά, δημιουργούμε ένα νέο παράδειγμα της τελειωμένης κλάσης TreeViewExample και εκτελούμε την εφαρμογή.

# Δείγμα πλήρους κώδικα

# Τεκμηρίωση σε βάθος

Σε αυτό το παράδειγμα χρησιμοποιήσαμε τα παρακάτω:

Εφαρμογή Gtk

Gtk.ApplicationWindow

Gtk.CellRendererText

Gtk.ListStore

Gtk.TreeIter

Gtk.TreeSelection

Gtk.TreeView

Gtk.TreeViewColumn