GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Erstellen Sie mit Ruby eine Linux-Desktopanwendung

Als ich kürzlich mit GTK und seinen Ruby-Bindungen experimentierte, beschloss ich, ein Tutorial zu schreiben, das diese Funktionalität vorstellt. In diesem Beitrag erstellen wir eine einfache ToDo-Anwendung (etwas wie das, was wir mit Ruby on Rails erstellt haben) mit dem gtk3 gem (auch bekannt als GTK+ Ruby-Bindungen).

Den Code des Tutorials finden Sie auf GitHub.

Was ist GTK+?

Laut GTK+-Website:

GTK+ oder das GIMP Toolkit ist ein plattformübergreifendes Toolkit zum Erstellen grafischer Benutzeroberflächen. GTK+ bietet einen vollständigen Satz von Widgets und eignet sich für Projekte, die von kleinen einmaligen Tools bis hin zu kompletten Anwendungspaketen reichen.

Die Website erklärt auch, warum GTK+ erstellt wurde:

GTK+ wurde ursprünglich für GIMP, das GNU Image Manipulation Program, entwickelt und verwendet. Es heißt "The GIMP ToolKit", damit die Ursprünge des Projekts nicht vergessen werden. Heute ist es eher kurz als GTK+ bekannt und wird von einer großen Anzahl von Anwendungen verwendet, einschließlich des GNOME-Desktops des GNU-Projekts.

Voraussetzungen

GTK+:

Verwandte Inhalte

Stellen Sie sicher, dass GTK+ installiert ist. Ich habe die Anwendung des Tutorials in Ubuntu 16.04 entwickelt, auf dem standardmäßig GTK+ (Version 3.18) installiert ist.

Sie können Ihre Version mit dem folgenden Befehl überprüfen: dpkg -l libgtk-3-0 .

Ruby:

Sie sollten Ruby auf Ihrem System installiert haben. Ich verwende RVM, um mehrere auf meinem System installierte Ruby-Versionen zu verwalten. Wenn Sie das auch tun möchten, finden Sie RVM-Installationsanweisungen auf seiner Homepage und Anweisungen zum Installieren von Ruby-Versionen (alias Rubies) auf der zugehörigen Dokumentationsseite.

Dieses Tutorial verwendet Ruby 2.4.2. Sie können Ihre Version mit ruby --version überprüfen oder über RVM mit rvm list .

Lichtung:

Auf der Glade-Website heißt es:„Glade ist ein RAD-Tool zur schnellen und einfachen Entwicklung von Benutzeroberflächen für das GTK+-Toolkit und die GNOME-Desktopumgebung.“

Wir werden Glade verwenden, um die Benutzeroberfläche unserer Anwendung zu entwerfen. Wenn Sie Ubuntu verwenden, installieren Sie glade mit sudo apt install glade .

GTK3-Juwel:

Dieses Juwel stellt die Ruby-Bindungen für das GTK+-Toolkit bereit. Mit anderen Worten, es ermöglicht uns, mit der GTK+-API unter Verwendung der Ruby-Sprache zu kommunizieren.

Installieren Sie das Gem mit gem install gtk3 .

Anwendungsspezifikationen definieren

Die Anwendung, die wir in diesem Tutorial erstellen, wird:

  • Eine Benutzeroberfläche haben (d. h. eine Desktop-Anwendung)
  • Benutzern erlauben, verschiedene Eigenschaften für jedes Element festzulegen (z. B. Priorität)
  • Benutzern erlauben, ToDo-Einträge zu erstellen und zu bearbeiten
    • Alle Elemente werden als Dateien im Home-Verzeichnis des Benutzers in einem Ordner namens .gtk-todo-tutorial gespeichert
  • Benutzern erlauben, ToDo-Einträge zu archivieren
    • Archivierte Elemente sollten in einem eigenen Ordner namens archiviert abgelegt werden

Anwendungsstruktur

gtk-todo-tutorial # root directory
  |-- application
    |-- ui # everything related to the ui of the application
    |-- models # our models
    |-- lib # the directory to host any utilities we might need
  |-- resources # directory to host the resources of our application
  gtk-todo # the executable that will start our application

Aufbau der ToDo-Anwendung

Initialisierung der Anwendung

Erstellen Sie ein Verzeichnis, um alle Dateien zu speichern, die die Anwendung benötigt. Wie Sie in der obigen Struktur sehen können, habe ich mein gtk-todo-tutorial genannt .

Erstellen Sie eine Datei namens gtk-todo (das ist richtig, keine Erweiterung) und fügen Sie Folgendes hinzu:

#!/usr/bin/env ruby

require 'gtk3'

app = Gtk::Application.new 'com.iridakos.gtk-todo', :flags_none

app.signal_connect :activate do |application|
  window = Gtk::ApplicationWindow.new(application)
  window.set_title 'Hello GTK+Ruby!'
  window.present
end

puts app.run

Dies ist das Skript, das die Anwendung startet.

Beachten Sie den Kram (#! ) in der ersten Zeile. So definieren wir, welcher Interpreter das Skript unter Unix/Linux-Betriebssystemen ausführt. Auf diese Weise müssen wir ruby gtk-todo nicht verwenden; wir können einfach den Namen des Skripts verwenden:gtk-todo .

Probieren Sie es jedoch noch nicht aus, da wir den Modus der Datei nicht so geändert haben, dass sie ausführbar ist. Geben Sie dazu den folgenden Befehl in ein Terminal ein, nachdem Sie zum Stammverzeichnis der Anwendung navigiert sind:

chmod +x ./gtk-todo # make the script executable

Führen Sie in der Konsole Folgendes aus:

./gtk-todo # execute the script

Anmerkungen:

  • Das oben definierte Anwendungsobjekt (und alle GTK+-Widgets im Allgemeinen) senden Signale aus, um Ereignisse auszulösen. Sobald beispielsweise eine Anwendung zu laufen beginnt, gibt sie ein Signal aus, um das activate auszulösen Veranstaltung. Alles, was wir tun müssen, ist zu definieren, was passieren soll, wenn dieses Signal gesendet wird. Wir haben dies erreicht, indem wir den signal_connect verwendet haben instance-Methode und übergibt ihr einen Block, dessen Code bei dem gegebenen Ereignis ausgeführt wird. Wir werden dies während des gesamten Tutorials häufig tun.
  • Als wir die Gtk::Application initialisiert haben Objekt haben wir zwei Parameter übergeben:
    • com.iridakos.gtk-todo :Dies ist die ID unserer Anwendung und sollte im Allgemeinen eine ID im Reverse-DNS-Stil sein. Im GNOME-Wiki können Sie mehr über die Verwendung und bewährte Verfahren erfahren.
    • :flags_none :Dieses Flag definiert das Verhalten der Anwendung. Wir haben das Standardverhalten verwendet. Schauen Sie sich alle Flags und die Arten von Anwendungen an, die sie definieren. Wir können die Ruby-äquivalenten Flags verwenden, wie in Gio::ApplicationFlags.constants definiert . Anstatt beispielsweise :flags_none zu verwenden , könnten wir Gio::ApplicationFlags::FLAGS_NONE verwenden .

Angenommen, das zuvor erstellte Anwendungsobjekt (Gtk::Application ) hatte beim aktivieren einiges zu tun Signal ausgegeben wurde oder dass wir uns mit weiteren Signalen verbinden wollten. Wir würden am Ende ein riesiges gtk-todo erstellen script-Datei, was das Lesen/Warten erschwert. Es ist Zeit für eine Umgestaltung.

Wie oben in der Anwendungsstruktur beschrieben, erstellen wir einen Ordner mit dem Namen application und Unterordner ui , Modelle , und lib .

  • Im ui Ordner, werden wir alle Dateien ablegen, die sich auf unsere Benutzeroberfläche beziehen.
  • In den Modellen Ordner, werden wir alle Dateien ablegen, die sich auf unsere Modelle beziehen.
  • In der lib Ordner, werden wir alle Dateien ablegen, die zu keiner dieser Kategorien gehören.

Wir werden eine neue Unterklasse von Gtk::Application definieren Klasse für unsere Anwendung. Wir erstellen eine Datei mit dem Namen application.rb unter application/ui/todo mit folgendem Inhalt:

module ToDo
  class Application < Gtk::Application
    def initialize
      super 'com.iridakos.gtk-todo', Gio::ApplicationFlags::FLAGS_NONE

      signal_connect :activate do |application|
        window = Gtk::ApplicationWindow.new(application)
        window.set_title 'Hello GTK+Ruby!'
        window.present
      end
    end
  end
end

Wir ändern das gtk-todo script entsprechend:

#!/usr/bin/env ruby

require 'gtk3'

app = ToDo::Application.new

puts app.run

Viel sauberer, oder? Ja, aber es geht nicht. Wir bekommen so etwas wie:

./gtk-todo:5:in `<main>': uninitialized constant ToDo (NameError)

Das Problem ist, dass wir keine der Ruby-Dateien in der Anwendung benötigt haben Mappe. Wir müssen die Skriptdatei wie folgt ändern und erneut ausführen.

#!/usr/bin/env ruby

require 'gtk3'

# Require all ruby files in the application folder recursively
application_root_path = File.expand_path(__dir__)
Dir[File.join(application_root_path, '**', '*.rb')].each { |file| require file }

app = ToDo::Application.new

puts app.run

Jetzt sollte es in Ordnung sein.

Ressourcen

Zu Beginn dieses Tutorials haben wir gesagt, dass wir Glade verwenden würden, um die Benutzeroberfläche der Anwendung zu entwerfen. Glade erzeugt xml Dateien mit den entsprechenden Elementen und Attributen, die widerspiegeln, was wir über die Benutzeroberfläche entworfen haben. Wir müssen diese Dateien für unsere Anwendung verwenden, um die von uns entworfene Benutzeroberfläche zu erhalten.

Diese Dateien sind Ressourcen für die Anwendung und die GResource Die API bietet eine Möglichkeit, sie alle zusammen in eine Binärdatei zu packen, auf die später aus der Anwendung heraus zugegriffen werden kann, mit Vorteilen – im Gegensatz zum manuellen Umgang mit bereits geladenen Ressourcen, ihrem Speicherort im Dateisystem usw. Lesen Sie mehr über die GResource API.

Beschreibung der Ressourcen

Zuerst müssen wir eine Datei erstellen, die die Ressourcen der Anwendung beschreibt. Erstellen Sie eine Datei namens gresources.xml und platzieren Sie es direkt unter den Ressourcen Ordner.

<?xml version="1.0" encoding="UTF-8"?>
<gresources>
  <gresource prefix="/com/iridakos/gtk-todo">
    <file preprocess="xml-stripblanks">ui/application_window.ui</file>
  </gresource>
</gresources>

Diese Beschreibung besagt im Wesentlichen:„Wir haben eine Ressource, die sich unter ui befindet Verzeichnis (relativ zu diesem xml Datei) mit dem Namen application_window.ui . Bevor Sie diese Ressource laden, entfernen Sie bitte die Leerzeichen." Das funktioniert natürlich noch nicht, da wir die Ressource nicht über Glade erstellt haben. Aber keine Sorge, eins nach dem anderen.

Hinweis :Die xml-stripblanks Direktive verwendet den xmllint Befehl zum Entfernen der Leerzeichen. In Ubuntu müssen Sie das Paket libxml2-utils installieren .

Erstellen der Ressourcen-Binärdatei

Um die binäre Ressourcendatei zu erstellen, verwenden wir ein anderes GLib-Bibliotheksdienstprogramm namens glib-compile-resources . Überprüfen Sie mit dpkg -l libglib2.0-bin, ob Sie es installiert haben . Sie sollten so etwas sehen:

ii  libglib2.0-bin     2.48.2-0ubuntu amd64          Programs for the GLib library

Wenn nicht, installieren Sie das Paket (sudo apt install libglib2.0-bin in Ubuntu).

Lassen Sie uns die Datei erstellen. Wir werden unserem Skript Code hinzufügen, damit die Ressourcen bei jeder Ausführung erstellt werden. Ändern Sie das gtk-todo Skript wie folgt:

#!/usr/bin/env ruby

require 'gtk3'
require 'fileutils'

# Require all ruby files in the application folder recursively
application_root_path = File.expand_path(__dir__)
Dir[File.join(application_root_path, '**', '*.rb')].each { |file| require file }

# Define the source & target files of the glib-compile-resources command
resource_xml = File.join(application_root_path, 'resources', 'gresources.xml')
resource_bin = File.join(application_root_path, 'gresource.bin')

# Build the binary
system("glib-compile-resources",
       "--target", resource_bin,
       "--sourcedir", File.dirname(resource_xml),
       resource_xml)

at_exit do
  # Before existing, please remove the binary we produced, thanks.
  FileUtils.rm_f(resource_bin)
end

app = ToDo::Application.new
puts app.run

Wenn wir es ausführen, passiert Folgendes in der Konsole:Wir werden es später beheben:

/.../gtk-todo-tutorial/resources/gresources.xml: Failed to locate 'ui/application_window.ui' in any source directory.

Folgendes haben wir getan:

  • require hinzugefügt -Anweisung für die fileutils Bibliothek, damit wir sie im at_exit verwenden können anrufen
  • Quell- und Zieldateien der glib-compile-resources definiert Befehl
  • Die glib-compile-resources ausgeführt Befehl
  • Setzen Sie einen Haken, damit die Binärdatei gelöscht wird, bevor das Skript beendet wird (d. h. bevor die Anwendung beendet wird), damit sie beim nächsten Mal erneut erstellt wird

Laden der Binärdatei der Ressourcen

Wir haben die Ressourcen beschrieben und in eine Binärdatei gepackt. Jetzt müssen wir sie in der Anwendung laden und registrieren, damit wir sie verwenden können. Dies ist so einfach wie das Hinzufügen der folgenden zwei Zeilen vor dem at_exit Haken:

resource = Gio::Resource.load(resource_bin)
Gio::Resources.register(resource)

Das ist es. Von nun an können wir die Ressourcen von überall innerhalb der Anwendung nutzen. (Wir werden später sehen, wie.) Im Moment schlägt das Skript fehl, da es keine Binärdatei laden kann, die nicht erstellt wurde. Sei geduldig; wir kommen bald zum interessanten Teil. Eigentlich jetzt.

Entwerfen des Hauptanwendungsfensters

Wir stellen Glade vor

Öffnen Sie zunächst Glade.

Folgendes sehen wir:

  • Auf der linken Seite gibt es eine Liste von Widgets, die per Drag &Drop in den mittleren Abschnitt gezogen werden können. (Sie können kein Top-Level-Fenster innerhalb eines Label-Widgets hinzufügen.) Ich nenne dies den Widget-Abschnitt .
  • Der mittlere Abschnitt enthält unsere Widgets, wie sie (meistens) in der Anwendung erscheinen werden. Ich nenne dies den Designbereich .
  • Rechts sind zwei Unterabschnitte:
    • Der obere Abschnitt enthält die Hierarchie der Widgets, wenn sie der Ressource hinzugefügt werden. Ich nenne dies den Hierarchieabschnitt .
    • Der untere Abschnitt enthält alle Eigenschaften, die über Glade für ein oben ausgewähltes Widget konfiguriert werden können. Ich nenne dies den Abschnitt Eigenschaften .

Ich werde die Schritte zum Erstellen der Benutzeroberfläche dieses Tutorials mit Glade beschreiben, aber wenn Sie daran interessiert sind, GTK+-Anwendungen zu erstellen, sollten Sie sich die offiziellen Ressourcen und Tutorials des Tools ansehen.

Erstellen Sie das Design des Anwendungsfensters

Lassen Sie uns das Anwendungsfenster erstellen, indem Sie einfach das Anwendungsfenster ziehen Widget aus dem Widget-Bereich in den Design-Bereich.

Gtk::Builder ist ein Objekt, das in GTK+-Anwendungen verwendet wird, um Textbeschreibungen einer Benutzeroberfläche (wie die, die wir über Glade erstellen werden) zu lesen und die beschriebenen Objekt-Widgets zu erstellen.

Das Erste im Abschnitt Eigenschaften ist die ID , und es hat einen Standardwert applicationWindow1 . Wenn wir diese Eigenschaft unverändert lassen, würden wir später einen Gtk::Builder erstellen durch unseren Code, der die von Glade erzeugte Datei lädt. Um das Anwendungsfenster zu erhalten, müssten wir etwas verwenden wie:

application_window = builder.get_object('applicationWindow1')

application_window.signal_connect 'whatever' do |a,b|
...

Das application_window Objekt wäre von der Klasse Gtk::ApplicationWindow; daher würde alles, was wir zu seinem Verhalten hinzufügen müssten (wie das Festlegen seines Titels), außerhalb der ursprünglichen Klasse stattfinden. Außerdem würde, wie im obigen Snippet gezeigt, der Code zum Verbinden mit einem Fenstersignal in der Datei platziert werden, die es instanziiert hat.

Die gute Nachricht ist, dass GTK+ 2013 eine Funktion eingeführt hat, die die Erstellung zusammengesetzter Widget-Vorlagen ermöglicht, die es uns (neben anderen Vorteilen) ermöglichen, die benutzerdefinierte Klasse für das Widget zu definieren (die schließlich von einem vorhandenen GTK::Widget Klasse allgemein). Machen Sie sich keine Sorgen, wenn Sie verwirrt sind. Sie werden verstehen, was vor sich geht, nachdem wir etwas Code geschrieben und die Ergebnisse angesehen haben.

Um unser Design als Vorlage zu definieren, markieren Sie Composite Kontrollkästchen im Eigenschaften-Widget. Beachten Sie, dass die ID Eigenschaft in Klassenname geändert . Füllen Sie TodoApplicationWindow aus . Dies ist die Klasse, die wir in unserem Code erstellen werden, um dieses Widget darzustellen.

Speichern Sie die Datei unter dem Namen application_window.ui in einem neuen Ordner namens ui innerhalb der Ressourcen . Folgendes sehen wir, wenn wir die Datei in einem Editor öffnen:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
  <requires lib="gtk+" version="3.12"/>
  <template class="TodoApplicationWindow" parent="GtkApplicationWindow">
    <property name="can_focus">False</property>
    <child>
      <placeholder/>
    </child>
  </template>
</interface>

Unser Widget hat eine Klasse und ein übergeordnetes Attribut. Gemäß der Konvention für übergeordnete Klassenattribute muss unsere Klasse in einem Modul namens Todo definiert werden . Bevor wir dorthin gelangen, versuchen wir, die Anwendung zu starten, indem wir das Skript ausführen (./gtk-todo ).

Ja! Es geht los!

Erstellen Sie die Anwendungsfensterklasse

Wenn wir den Inhalt des Stammverzeichnisses der Anwendung überprüfen, während die Anwendung ausgeführt wird, können wir die gresource.bin sehen Datei dort. Obwohl die Anwendung erfolgreich gestartet wird, da der Ressourcenbehälter vorhanden ist und registriert werden kann, verwenden wir ihn noch nicht. Wir werden immer noch ein gewöhnliches Gtk::ApplicationWindow initiieren in unserer application.rb Datei. Jetzt ist es an der Zeit, unsere benutzerdefinierte Anwendungsfensterklasse zu erstellen.

Erstellen Sie eine Datei namens application_window.rb in application/ui/todo Ordner und fügen Sie den folgenden Inhalt hinzu:

module Todo
  class ApplicationWindow < Gtk::ApplicationWindow
    # Register the class in the GLib world
    type_register

    class << self
      def init
        # Set the template from the resources binary
        set_template resource: '/com/iridakos/gtk-todo/ui/application_window.ui'
      end
    end

    def initialize(application)
      super application: application

      set_title 'GTK+ Simple ToDo'
    end
  end
end

Wir haben den init definiert -Methode als Singleton-Methode auf der Klasse nach dem Öffnen der Eigenklasse um die Vorlage dieses Widgets an die zuvor registrierte Ressourcendatei zu binden.

Davor haben wir das type_register aufgerufen class-Methode, die unsere benutzerdefinierte Widget-Klasse registriert und der GLib zur Verfügung stellt Welt.

Schließlich setzen wir jedes Mal, wenn wir eine Instanz dieses Fensters erstellen, seinen Titel auf GTK+ Simple ToDo .

Gehen wir nun zurück zur application.rb Datei und verwenden Sie, was wir gerade implementiert haben:

module ToDo
  class Application < Gtk::Application
    def initialize
      super 'com.iridakos.gtk-todo', Gio::ApplicationFlags::FLAGS_NONE

      signal_connect :activate do |application|
        window = Todo::ApplicationWindow.new(application)
        window.present
      end
    end
  end
end

Führen Sie das Skript aus.

Modell definieren

Der Einfachheit halber speichern wir die ToDo-Elemente in Dateien im JSON-Format in einem dedizierten versteckten Ordner im Home-Verzeichnis unseres Benutzers. In einer echten Anwendung würden wir eine Datenbank verwenden, aber das würde den Rahmen dieses Tutorials sprengen.

Unser Todo::Item Das Modell hat die folgenden Eigenschaften:

  • id :Die ID des Artikels
  • Titel :Der Titel
  • Notizen :Irgendwelche Anmerkungen
  • Priorität :Seine Priorität
  • creation_datetime :Das Datum und die Uhrzeit, zu der das Element erstellt wurde
  • Dateiname :Der Name der Datei, in der ein Element gespeichert wird

Wir erstellen eine Datei namens item.rb unter application/models Verzeichnis mit folgendem Inhalt:

require 'securerandom'
require 'json'

module Todo
  class Item
    PROPERTIES = [:id, :title, :notes, :priority, :filename, :creation_datetime].freeze

    PRIORITIES = ['high', 'medium', 'normal', 'low'].freeze

    attr_accessor *PROPERTIES

    def initialize(options = {})
      if user_data_path = options[:user_data_path]
        # New item. When saved, it will be placed under the :user_data_path value
        @id = SecureRandom.uuid
        @creation_datetime = Time.now.to_s
        @filename = "#{user_data_path}/#{id}.json"
      elsif filename = options[:filename]
        # Load an existing item
        load_from_file filename
      else
        raise ArgumentError, 'Please specify the :user_data_path for new item or the :filename to load existing'
      end
    end

    # Loads an item from a file
    def load_from_file(filename)
      properties = JSON.parse(File.read(filename))

      # Assign the properties
      PROPERTIES.each do |property|
        self.send "#{property}=", properties[property.to_s]
      end
    rescue => e
      raise ArgumentError, "Failed to load existing item: #{e.message}"
    end

    # Resolves if an item is new
    def is_new?
      !File.exists? @filename
    end

    # Saves an item to its `filename` location
    def save!
      File.open(@filename, 'w') do |file|
        file.write self.to_json
      end
    end

    # Deletes an item
    def delete!
      raise 'Item is not saved!' if is_new?

      File.delete(@filename)
    end

    # Produces a json string for the item
    def to_json
      result = {}
      PROPERTIES.each do |prop|
        result[prop] = self.send prop
      end

      result.to_json
    end
  end
end

Hier haben wir Methoden definiert zu:

  • Ein Element initialisieren:
    • Als "neu" durch Definition des :user_data_path in dem es später gespeichert wird
    • Als "vorhanden" durch Definition von :filename von geladen werden. Der Dateiname muss eine JSON-Datei sein, die zuvor von einem Element generiert wurde
  • Laden Sie ein Element aus einer Datei
  • Ermitteln Sie, ob ein Element neu ist oder nicht (d. h. mindestens einmal im :user_data_path gespeichert wurde oder nicht)
  • Speichern Sie ein Element, indem Sie seine JSON-Zeichenfolge in eine Datei schreiben
  • Element löschen
  • Erzeugen Sie den JSON-String eines Elements als Hash seiner Eigenschaften

Neuen Artikel hinzufügen

Schaltfläche erstellen

Fügen wir unserem Anwendungsfenster eine Schaltfläche zum Hinzufügen eines neuen Elements hinzu. Öffnen Sie die resources/ui/application_window.ui Datei in Glade.

  • Ziehen Sie eine Schaltfläche vom Widget-Bereich zum Design-Bereich.
  • Legen Sie im Abschnitt "Eigenschaften" seine ID fest Wert zu add_new_item_button .
  • Unten am General Registerkarte im Abschnitt Eigenschaften gibt es einen Textbereich direkt unter dem Etikett mit optionalem Bild Möglichkeit. Ändern Sie seinen Wert von Button zum Neuen Artikel hinzufügen .
  • Speichern Sie die Datei und führen Sie das Skript aus.

Mach dir keine Sorgen; Wir werden das Design später verbessern. Sehen wir uns nun an, wie Sie sich verbinden Funktionalität für unsere Schaltfläche, die angeklickt wird Veranstaltung.

Zuerst müssen wir unsere Anwendungsfensterklasse aktualisieren, damit sie von ihrem neuen untergeordneten Element, der Schaltfläche mit der ID add_new_item_button, erfährt . Dann können wir auf das Kind zugreifen, um sein Verhalten zu ändern.

Ändern Sie die init Methode wie folgt:

def init
  # Set the template from the resources binary
  set_template resource: '/com/iridakos/gtk-todo/ui/application_window.ui'

  bind_template_child 'add_new_item_button'
end

Ziemlich einfach, oder? Das bind_template_child -Methode macht genau das, was sie sagt, und von nun an jede Instanz unseres Todo::ApplicationWindow Klasse wird einen add_new_item_button haben -Methode, um auf die zugehörige Schaltfläche zuzugreifen. Also ändern wir den initialize Methode wie folgt:

def initialize(application)
  super application: application

  set_title 'GTK+ Simple ToDo'

  add_new_item_button.signal_connect 'clicked' do |button, application|
    puts "OMG! I AM CLICKED"
  end
end

Wie Sie sehen können, greifen wir über add_new_item_button auf die Schaltfläche zu -Methode, und wir definieren, was geschehen soll, wenn darauf geklickt wird. Starten Sie die Anwendung neu und versuchen Sie, auf die Schaltfläche zu klicken. In der Konsole sollte die Meldung OMG! ICH WERDE GEKLICKT wenn Sie auf die Schaltfläche klicken.

Wir möchten jedoch, dass beim Klicken auf diese Schaltfläche ein neues Fenster zum Speichern eines ToDo-Elements angezeigt wird. Sie haben richtig geraten:Es ist Glade Uhr.

Neues Elementfenster erstellen

  • Erstellen Sie ein neues Projekt in Glade, indem Sie auf das Symbol ganz links in der oberen Leiste klicken oder Datei> Neu auswählen aus dem Anwendungsmenü.
  • Ziehen Sie ein Fenster vom Widget-Bereich zum Design-Bereich.
  • Prüfen Sie sein Composite -Eigenschaft und nennen Sie die Klasse TodoNewItemWindow .

  • Ziehen Sie ein Raster aus dem Widget-Bereich und platzieren Sie es in dem zuvor hinzugefügten Fenster.
  • Stellen Sie 5 ein Zeilen und 2 Spalten in dem sich öffnenden Fenster.
  • Im Allgemeinen Legen Sie auf der Registerkarte "Eigenschaften" den Zeilen- und Spaltenabstand auf 10 fest (Pixel).
  • Im Gemeinsamen Legen Sie auf der Registerkarte "Eigenschaften" den Widget-Abstand> Ränder> Oben, Unten, Links, Rechts fest alle bis 10 damit der Inhalt nicht an den Rändern des Rasters klebt.

  • Ziehen Sie vier Label Widgets aus dem Widget-Bereich und platzieren Sie eines in jeder Zeile des Rasters.
  • Ändern Sie ihr Label Eigenschaften von oben nach unten wie folgt:
    • ID:
    • Titel:
    • Anmerkungen:
    • Priorität:
  • Im Allgemeinen Ändern Sie auf der Registerkarte "Eigenschaften" die Option Ausrichtung und Auffüllung> Ausrichtung> Horizontal Eigenschaft von 0,50 auf 1 für jede Eigenschaft, um den Beschriftungstext rechtsbündig auszurichten.
  • Dieser Schritt ist optional, wird aber empfohlen. Wir werden diese Labels nicht in unser Fenster binden, da wir ihren Status oder ihr Verhalten nicht ändern müssen. In diesem Zusammenhang müssen wir für sie keine beschreibende ID festlegen, wie wir es für den add_new_item_button getan haben Schaltfläche im Anwendungsfenster. ABER Wir werden unserem Design weitere Elemente hinzufügen, und die Hierarchie der Widgets in Glade wird schwer zu lesen sein, wenn sie label1 sagen , label2 , usw. Setzen beschreibender IDs (wie id_label , title_label , notes_label , priority_label ) wird unser Leben leichter machen. Ich habe sogar die ID des Grids auf main_grid gesetzt weil ich keine Zahlen oder Variablennamen in IDs sehe.

  • Ziehen Sie ein Label aus dem Widget-Bereich in die zweite Spalte der ersten Zeile des Rasters. Die ID wird automatisch von unserem Modell generiert; wir erlauben keine Bearbeitung, also ist ein Label zur Anzeige mehr als genug.
  • Stellen Sie die ID ein Eigenschaft zu id_value_label .
  • Stellen Sie Ausrichtung und Abstand> Ausrichtung> Horizontal ein -Eigenschaft auf 0 also wird der Text linksbündig ausgerichtet.
  • Wir werden dieses Widget an unsere Window-Klasse binden, damit wir seinen Text jedes Mal ändern können, wenn wir das Fenster laden. Daher ist es nicht erforderlich, eine Beschriftung über Glade festzulegen, aber es nähert das Design dem an, wie es aussehen wird, wenn es mit tatsächlichen Daten gerendert wird. Sie können ein Etikett so einstellen, wie es Ihnen am besten passt. Ich habe meine auf id-of-the-todo-item-here gesetzt .

  • Ziehen Sie einen Texteintrag aus dem Widget-Bereich in die zweite Spalte der zweiten Reihe des Rasters.
  • Setzen Sie seine ID-Eigenschaft auf title_text_entry . Wie Sie vielleicht bemerkt haben, ziehe ich es vor, den Widget-Typ in der ID zu erhalten, um den Code in der Klasse besser lesbar zu machen.
  • Im Gemeinsamen Aktivieren Sie auf der Registerkarte "Eigenschaften" die Option Widgetabstand> Erweitern> Horizontal Kontrollkästchen und schalten Sie den Schalter daneben ein. Auf diese Weise wird das Widget jedes Mal horizontal erweitert, wenn die Größe seines übergeordneten Elements (auch bekannt als das Raster) geändert wird.

  • Ziehen Sie eine Textansicht vom Widget-Bereich zur zweiten Spalte der dritten Zeile des Rasters.
  • Stellen Sie seine ID ein zu Notizen . Nein, ich teste dich nur. Legen Sie seine ID fest Eigenschaft zu notes_text_view .
  • Im Gemeinsamen Aktivieren Sie auf der Registerkarte "Eigenschaften" die Option Widgetabstand> Erweitern> Horizontal, Vertikal Kontrollkästchen und aktivieren Sie die Schalter daneben. Auf diese Weise wird das Widget jedes Mal horizontal und vertikal erweitert, wenn sein übergeordnetes Element (das Raster) in der Größe geändert wird.

  • Ziehen Sie ein Kombinationsfeld vom Widget-Bereich zur zweiten Spalte der vierten Zeile des Rasters.
  • Stellen Sie seine ID ein zu priority_combo_box .
  • Im Gemeinsamen Aktivieren Sie auf der Registerkarte "Eigenschaften" die Option Widgetabstand> Erweitern> Horizontal Kontrollkästchen und schalten Sie den Schalter rechts davon ein. Dadurch kann das Widget jedes Mal horizontal erweitert werden, wenn sein übergeordnetes Element (das Raster) in der Größe geändert wird.
  • Dieses Widget ist ein Dropdown-Element. We will populate its values that can be selected by the user when it shows up inside our window class.

  • Drag a Button Box from the Widget section to the second column of the last row of the grid.
  • In the pop-up window, select 2 items.
  • In the General tab of the Properties section, set the Box Attributes> Orientation property to Horizontal .
  • In the General tab of the Properties section, set the Box Attributes> Spacing property to 10 .
  • In the Common tab of the Properties section, set the Widget Spacing> Alignment> Horizontal to Center .
  • Again, our code won't alter this widget, but you can give it a descriptive ID for readability. I named mine actions_box .

  • Drag two Button widgets and place one in each box of the button box widget we added in the previous step.
  • Set their ID properties to cancel_button and save_button , respectively.
  • In the General tab of the Properties window, set their Button Content> Label with option image property to Cancel and Save , respectively.

The window is ready. Save the file under resources/ui/new_item_window.ui .

It's time to port it into our application.

Implement the new item window class

Before implementing the new class, we must update our GResource description file (resources/gresources.xml ) to obtain the new resource:

<?xml version="1.0" encoding="UTF-8"?>
<gresources>
  <gresource prefix="/com/iridakos/gtk-todo">
    <file preprocess="xml-stripblanks">ui/application_window.ui</file>
    <file preprocess="xml-stripblanks">ui/new_item_window.ui</file>
  </gresource>
</gresources>

Now we can create the new window class. Create a file under application/ui/todo named new_item_window.rb and set its contents as follows:

module Todo
  class NewItemWindow < Gtk::Window
    # Register the class in the GLib world
    type_register

    class << self
      def init
        # Set the template from the resources binary
        set_template resource: '/com/iridakos/gtk-todo/ui/new_item_window.ui'
      end
    end

    def initialize(application)
      super application: application
    end
  end
end

There's nothing special here. We just changed the template resource to point to the correct file of our resources.

We have to change the add_new_item_button code that executes on the clicked signal to show the new item window. We'll go ahead and change that code in application_window.rb to this:

add_new_item_button.signal_connect 'clicked' do |button|
  new_item_window = NewItemWindow.new(application)
  new_item_window.present
end

Let's see what we have done. Start the application and click on the Add new item Taste. Tadaa!

But nothing happens when we press the buttons. Let's fix that.

First, we'll bind the UI widgets in the Todo::NewItemWindow class.

Change the init method to this:

def init
  # Set the template from the resources binary
  set_template resource: '/com/iridakos/gtk-todo/ui/new_item_window.ui'

  # Bind the window's widgets
  bind_template_child 'id_value_label'
  bind_template_child 'title_text_entry'
  bind_template_child 'notes_text_view'
  bind_template_child 'priority_combo_box'
  bind_template_child 'cancel_button'
  bind_template_child 'save_button'
end

This window will be shown when either creating or editing a ToDo item, so the new_item_window naming is not very valid. We'll refactor that later.

For now, we will update the window's initialize method to require one extra parameter for the Todo::Item to be created or edited. We can then set a more meaningful window title and change the child widgets to reflect the current item.

We'll change the initialize method to this:

def initialize(application, item)
  super application: application
  set_title "ToDo item #{item.id} - #{item.is_new? ? 'Create' : 'Edit' } Mode"

  id_value_label.text = item.id
  title_text_entry.text = item.title if item.title
  notes_text_view.buffer.text = item.notes if item.notes

  # Configure the combo box
  model = Gtk::ListStore.new(String)
  Todo::Item::PRIORITIES.each do |priority|
    iterator = model.append
    iterator[0] = priority
  end

  priority_combo_box.model = model
  renderer = Gtk::CellRendererText.new
  priority_combo_box.pack_start(renderer, true)
  priority_combo_box.set_attributes(renderer, "text" => 0)

  priority_combo_box.set_active(Todo::Item::PRIORITIES.index(item.priority)) if item.priority
end

Then we'll add the constant PRIORITIES in the application/models/item.rb file just below the PROPERTIES constant:

PRIORITIES = ['high', 'medium', 'normal', 'low'].freeze

What did we do here?

  • We set the window's title to a string containing the current item's ID and the mode (depending on whether the item is being created or edited).
  • We set the id_value_label text to display the current item's ID.
  • We set the title_text_entry text to display the current item's title.
  • We set the notes_text_view text to display the current item's notes.
  • We created a model for the priority_combo_box whose entries are going to have only one String Wert. At first sight, a Gtk::ListStore model might look a little confusing. Here's how it works.
    • Suppose we want to display in a combo box a list of country codes and their respective country names.
    • We would create a Gtk::ListStore defining that its entries would consist of two string values:one for the country code and one for the country name. Thus we would initialize the ListStore as: 
      model = Gtk::ListStore.new(String, String)
    • To fill the model with data, we would do something like the following (make sure you don't miss the comments in the snippet): 
      [['gr', 'Greece'], ['jp','Japan'], ['nl', 'Netherlands']].each do |country_pair|
        entry = model.append
        # Each entry has two string positions since that's how we initialized the Gtk::ListStore
        # Store the country code in position 0
        entry[0] = country_pair[0]
        # Store the country name in position 1
        entry[1] = country_pair[1]
      end
    • We also configured the combo box to render two text columns/cells (again, make sure you don't miss the comments in the snippet): 
      country_code_renderer = Gtk::CellRendererText.new
      # Add the first renderer
      combo.pack_start(country_code_renderer, true)
      # Use the value in index 0 of each model entry a.k.a. the country code
      combo.set_attributes(country_code_renderer, 'text' => 0)

      country_name_renderer = Gtk::CellRendererText.new
      # Add the second renderer
      combo.pack_start(country_name_renderer, true)
      # Use the value in index 1 of each model entry a.k.a. the country name
      combo.set_attributes(country_name_renderer, 'text' => 1)
    • I hope that made it a little clearer.
  • We added a simple text renderer in the combo box and instructed it to display the only value of each model's entry (a.k.a., position 0 ). Imagine that our model is something like [['high'],['medium'],['normal'],['low']] and 0 is the first element of each sub-array. I will stop with the model-combo-text-renderer explanations now…

Configure the user data path

Remember that when initializing a new Todo::Item (not an existing one), we had to define a :user_data_path in which it would be saved. We are going to resolve this path when the application starts and make it accessible from all the widgets.

All we have to do is check if the .gtk-todo-tutorial path exists inside the user's home ~ Verzeichnis. If not, we will create it. Then we'll set this as an instance variable of the application. All widgets have access to the application instance. So, all widgets have access to this user path variable.

Change the application/application.rb file to this:

module ToDo
  class Application < Gtk::Application
    attr_reader :user_data_path

    def initialize
      super 'com.iridakos.gtk-todo', Gio::ApplicationFlags::FLAGS_NONE

      @user_data_path = File.expand_path('~/.gtk-todo-tutorial')
      unless File.directory?(@user_data_path)
        puts "First run. Creating user's application path: #{@user_data_path}"
        FileUtils.mkdir_p(@user_data_path)
      end

      signal_connect :activate do |application|
        window = Todo::ApplicationWindow.new(application)
        window.present
      end
    end
  end
end

One last thing we need to do before testing what we have done so far is to instantiate the Todo::NewItemWindow when the add_new_item_button is clicked complying with the changes we made. In other words, change the code in application_window.rb to this:

add_new_item_button.signal_connect 'clicked' do |button|
  new_item_window = NewItemWindow.new(application, Todo::Item.new(user_data_path: application.user_data_path))
  new_item_window.present
end

Start the application and click on the Add new item Taste. Tadaa! (Note the - Create mode part in the title).

Cancel item creation/update

To close the Todo::NewItemWindow window when a user clicks the cancel_button , we only have to add this to the window's initialize method:

cancel_button.signal_connect 'clicked' do |button|
  close
end

close is an instance method of the Gtk::Window class that closes the window.

Save the item

Saving an item involves two steps:

  • Update the item's properties based on the widgets' values.
  • Call the save! method on the Todo::Item instance.

Again, our code will be placed in the initialize method of the Todo::NewItemWindow :

save_button.signal_connect 'clicked' do |button|
  item.title = title_text_entry.text
  item.notes = notes_text_view.buffer.text
  item.priority = priority_combo_box.active_iter.get_value(0) if priority_combo_box.active_iter
  item.save!
  close
end

Once again, the window closes after saving the item.

Let's try that out.

Now, by pressing Save and navigating to our ~/.gtk-todo-tutorial folder, we should see a file. Mine had the following contents:

{
        "id": "3d635839-66d0-4ce6-af31-e81b47b3e585",
        "title": "Optimize the priorities model creation",
        "notes": "It doesn't have to be initialized upon each window creation.",
        "priority": "high",
        "filename": "/home/iridakos/.gtk-todo-tutorial/3d635839-66d0-4ce6-af31-e81b47b3e585.json",
        "creation_datetime": "2018-01-25 18:09:51 +0200"
}

Don't forget to try out the Cancel button as well.

View ToDo items

The Todo::ApplicationWindow contains only one button. It's time to change that.

We want the window to have Add new item on the top and a list below with all of our ToDo items. We'll add a Gtk::ListBox to our design that can contain any number of rows.

Update the application window

  • Open the resources/ui/application_window.ui file in Glade.
  • Nothing happens if we drag a List Box widget from the Widget section directly on the window. Das ist normal. First, we have to split the window into two parts:one for the button and one for the list box. Bear with me.
  • Right-click on the new_item_window in the Hierarchy section and select Add parent> Box .
  • In the pop-up window, set 2 for the number of items.
  • The orientation of the box is already vertical, so we are fine.

  • Now, drag a List Box and place it on the free area of the previously added box.
  • Set its ID property to todo_items_list_box .
  • Set its Selection mode to None since we won't provide that functionality.

Design the ToDo item list box row

Each row of the list box we created in the previous step will be more complex than a row of text. Each will contain widgets that allow the user to expand an item's notes and to delete or edit the item.

  • Create a new project in Glade, as we did for the new_item_window.ui . Save it under resources/ui/todo_item_list_box_row.ui .
  • Unfortunately (at least in my version of Glade), there is no List Box Row widget in the Widget section. So, we'll add one as the top-level widget of our project in a kinda hackish way.
  • Drag a List Box from the Widget section to the Design area.
  • Inside the Hierarchy section, right-click on the List Box and select Add Row

  • In the Hierarchy section, right-click on the newly added List Box Row nested under the List Box and select Remove parent . Da ist es! The List Box Row is the top-level widget of the project now.

  • Check the widget's Composite property and set its name to TodoItemListBoxRow .
  • Drag a Box from the Widget section to the Design area inside our List Box Row .
  • Set 2 items in the pop-up window.
  • Set its ID property to main_box .

  • Drag another Box from the Widget section to the first row of the previously added box.
  • Set 2 items in the pop-up window.
  • Set its ID property to todo_item_top_box .
  • Set its Orientation property to Horizontal .
  • Set its Spacing (General tab) property to 10 .

  • Drag a Label from the Widget section to the first column of the todo_item_top_box .
  • Set its ID property to todo_item_title_label .
  • Set its Alignment and Padding> Alignment> Horizontal property to 0.00 .
  • In the Common tab of the Properties section, check the Widget Spacing> Expand> Horizontal checkbox and turn on the switch next to it so the label will expand to the available space.

  • Drag a Button from the Widget section to the second column of the todo_item_top_box .
  • Set its ID property to details_button .
  • Check the Button Content> Label with optional image radio and type ... (three dots).

  • Drag a Revealer widget from the Widget section to the second row of the main_box .
  • Turn off the Reveal Child switch in the General tab.
  • Set its ID property to todo_item_details_revealer .
  • Set its Transition type property to Slide Down .

  • Drag a Box from the Widget section to the reveal space.
  • Set its items to 2 in the pop-up window.
  • Set its ID property to details_box .
  • In the Common tab, set its Widget Spacing> Margins> Top property to 10 .

  • Drag a Button Box from the Widget section to the first row of the details_box .
  • Set its ID property to todo_item_action_box .
  • Set its Layout style property to expand .

  • Drag Button widgets to the first and second columns of the todo_item_action_box .
  • Set their ID properties to delete_button and edit_button , respectively.
  • Set their Button Content> Label with optional image properties to Delete and Edit , respectively.

  • Drag a Viewport widget from the Widget section to the second row of the details_box .
  • Set its ID property to todo_action_notes_viewport .
  • Drag a Text View widget from the Widget section to the todo_action_notes_viewport that we just added.
  • Set its ID to todo_item_notes_text_view .
  • Uncheck its Editable property in the General tab of the Properties section.

Create the ToDo item list-box row class

Now we will create the class reflecting the UI of the list-box row we just created.

First we have to update our GResource description file to include the newly created design. Change the resources/gresources.xml file as follows:

<?xml version="1.0" encoding="UTF-8"?>
<gresources>
  <gresource prefix="/com/iridakos/gtk-todo">
    <file preprocess="xml-stripblanks">ui/application_window.ui</file>
    <file preprocess="xml-stripblanks">ui/new_item_window.ui</file>
    <file preprocess="xml-stripblanks">ui/todo_item_list_box_row.ui</file>
  </gresource>
</gresources>

Create a file named item_list_box_row.rb inside the application/ui folder and add the following:

module Todo
  class ItemListBoxRow < Gtk::ListBoxRow
    type_register

    class << self
      def init
        set_template resource: '/com/iridakos/gtk-todo/ui/todo_item_list_box_row.ui'
      end
    end

    def initialize(item)
      super()
    end
  end
end

We will not bind any children at the moment.

When starting the application, we have to search for files in the :user_data_path , and we must create a Todo::Item instance for each file. For each instance, we must also add a new Todo::ItemListBoxRow to the Todo::ApplicationWindow 's todo_items_list_box list box. One thing at a time.

First, let's bind the todo_items_list_box in the Todo::ApplicationWindow Klasse. Change the init method as follows:

def init
  # Set the template from the resources binary
  set_template resource: '/com/iridakos/gtk-todo/ui/application_window.ui'

  bind_template_child 'add_new_item_button'
  bind_template_child 'todo_items_list_box'
end

Next, we'll add an instance method in the same class that will be responsible to load the ToDo list items in the related list box. Add this code in Todo::ApplicationWindow :

def load_todo_items
  todo_items_list_box.children.each { |child| todo_items_list_box.remove child }

  json_files = Dir[File.join(File.expand_path(application.user_data_path), '*.json')]
  items = json_files.map{ |filename| Todo::Item.new(filename: filename) }

  items.each do |item|
    todo_items_list_box.add Todo::ItemListBoxRow.new(item)
  end
end

Then we'll call this method at the end of the initialize method:

def initialize(application)
  super application: application

  set_title 'GTK+ Simple ToDo'

  add_new_item_button.signal_connect 'clicked' do |button|
    new_item_window = NewItemWindow.new(application, Todo::Item.new(user_data_path: application.user_data_path))
    new_item_window.present
  end

  load_todo_items
end

Hinweis: We must first empty the list box of its current children rows then refill it. This way, we will call this method after saving a Todo::Item via the signal_connect of the save_button of the Todo::NewItemWindow , and the parent application window will be reloaded! Here's the updated code (in application/ui/new_item_window.rb ):

save_button.signal_connect 'clicked' do |button|
  item.title = title_text_entry.text
  item.notes = notes_text_view.buffer.text
  item.priority = priority_combo_box.active_iter.get_value(0) if priority_combo_box.active_iter
  item.save!

  close

  # Locate the application window
  application_window = application.windows.find { |w| w.is_a? Todo::ApplicationWindow }
  application_window.load_todo_items
end

Previously, we used this code:

json_files = Dir[File.join(File.expand_path(application.user_data_path), '*.json')]

to find the names of all the files in the application-user data path with a JSON extension.

Let's see what we've created. Start the application and try adding a new ToDo item. After pressing the Save button, you should see the parent Todo::ApplicationWindow automatically updated with the new item!

What's left is to complete the functionality of the Todo::ItemListBoxRow .

First, we will bind the widgets. Change the init method of the Todo::ItemListBoxRow class as follows:

def init
  set_template resource: '/com/iridakos/gtk-todo/ui/todo_item_list_box_row.ui'

  bind_template_child 'details_button'
  bind_template_child 'todo_item_title_label'
  bind_template_child 'todo_item_details_revealer'
  bind_template_child 'todo_item_notes_text_view'
  bind_template_child 'delete_button'
  bind_template_child 'edit_button'
end

Then, we'll set up the widgets based on the item of each row.

def initialize(item)
  super()

  todo_item_title_label.text = item.title || ''

  todo_item_notes_text_view.buffer.text = item.notes

  details_button.signal_connect 'clicked' do
    todo_item_details_revealer.set_reveal_child !todo_item_details_revealer.reveal_child?
  end

  delete_button.signal_connect 'clicked' do
    item.delete!

    # Locate the application window
    application_window = application.windows.find { |w| w.is_a? Todo::ApplicationWindow }
    application_window.load_todo_items
  end

  edit_button.signal_connect 'clicked' do
    new_item_window = NewItemWindow.new(application, item)
    new_item_window.present
  end
end

def application
  parent = self.parent
  parent = parent.parent while !parent.is_a? Gtk::Window
  parent.application
end
  • As you can see, when the details_button is clicked, we instruct the todo_item_details_revealer to swap the visibility of its contents.
  • After deleting an item, we find the application's Todo::ApplicationWindow to call its load_todo_items , as we did after saving an item.
  • When clicking to edit a button, we create a new instance of the Todo::NewItemWindow passing an item as the current item. Works like a charm!
  • Finally, to reach the application parent of a list-box row, we defined a simple instance method application that navigates through the widget's parents until it reaches a window from which it can obtain the application object.

Save and run the application. There it is!

This has been a really long tutorial and, even though there are so many items that we haven't covered, I think we better end it here.

Long post, cat photo.

  • This tutorial's code
  • A set of bindings for the GNOME-2.x libraries to use from Ruby
  • Gtk3 tutorial for Ruby based on the official C version
  • GTK+ 3 Reference Manual

This was originally published on Lazarus Lazaridis's blog, iridakos.com, and is republished with permission.


Linux
  1. Behalten Sie mit dieser Desktop-Anwendung die technischen Daten Ihres Linux-Computers im Auge

  2. Erstellen Sie ein SDN unter Linux mit Open Source

  3. Erstellen Sie ein einzigartiges Linux-Erlebnis mit der Unix-Desktop-Umgebung

  4. Passen Sie Ihren Linux-Desktop mit KDE Plasma an

  5. Wie wir mit Electron eine Linux-Desktop-App erstellt haben

So erstellen Sie Verknüpfungen auf dem Linux-Desktop

Erstellen Sie ein bootfähiges USB-Laufwerk mit USBImager unter Linux

So verwenden Sie PostgreSQL mit der Ruby On Rails-Anwendung

So erstellen Sie eine Volumengruppe in Linux mit LVM

Genießen Sie Twitch unter Linux mit der GNOME Twitch-Anwendung

Wie erstelle ich eine Desktop-Verknüpfung?