Taryn Fox
jewelfox@fursona.net 2012
Lär dig hur du placerar ut komponenter för användargränssnitt, så som bilder och etiketter.
Sebastian Rasmussen
sebras@gmail.com 2019
Anders Jonsson
anders.jonsson@norsjovallen.se 2021

# 2. Välkommen till rutnätet

        Denna handledning kommer att visa dig hur du skapar grundläggande komponenter eller delar av ett GNOME-användargränssnitt, så som bilder (Image) och etiketter (Label). Du kommer därefter att få lära dig att arrangera dem i ett rutnät (Grid), vilket låter dig placera komponenter precis där du vill ha dem.
        Har du redan gått genom den första handledningen i denna serie? Du kommer att behöva ha gjort det innan du fortsätter.

# Att köra inhemskt

I den senaste handledningen skapade vi vad som praktiskt taget var en GNOME-fönsterram för en webbapplikation. All GNOME-specifik kod som vi behövde lära oss kretsade kring att sätta denna WebView — komponenten som innehåller vårt program — i ett ApplicationWindow, och säga till den att visas. Programmet själv skrevs i HTML och Javascript, precis som de flesta sidor på webben.

Denna gång kommer vi att enbart använda inhemska GNOME-komponenter. En komponent är en sak, så som en kryssruta eller en bild, och GNOME har en uppsjö att välja bland. Vi kallar dem ”inhemska” komponenter för att särskilja dem från saker som knappen och rubriken i webbapplikationen vi skrev. Istället för att använda webbkod kommer dessa att vara 100 procent GNOME, genom att använda GTK+.

GTK+ står för ”GIMP Tookit”. Det är som en verktygslåda av komponenter som du kan använda när du bygger dina program. Det skrevs från början för GIMP, vilket är fri programvara för att redigera bilder.

# Konfigurera ditt program

Innan vi gräver fram några komponenter från GTK+-verktygslådan måste vi först skriva den grundläggande standardkoden som vårt program kräver.

#!/usr/bin/gjs

imports.gi.versions.Gtk = '3.0'; const Gtk = imports.gi.Gtk;

Denna del finns alltid i början av din kod. Beroende på vad du ska göra med det kan du komma att vilja deklarera fler importer här. Det vi skriver idag är ganska grundläggande så detta är allt vi behöver; Gtk för komponenter, via det stabila API:t ”3.0”.

På tal om det:

class WelcomeToTheGrid { // Skapa programmet i sig constructor() { this.application = new Gtk.Application();

// Anslut ”activate”- och ”startup”-signaler till återanropsfunktionerna this.application.connect('activate', this._onActivate.bind(this)); this.application.connect('startup', this._onStartup.bind(this)); }

// Återanropsfunktion för ”activate”-signal visar fönster när den aktiveras _onActivate() { this._window.present(); }

// Återanropsfunktion för ”startup”-signal bygger användargränssnittet _onStartup() { this._buildUI (); }

Detta är starten på programmet i sig, och _init-funktionen som skapar det. Det berättar för _buildUI att skapa ett ApplicationWindow, vilket vi kommer kalla för _window, och berätta för vårt fönster att presentera sig sig själv närhelst det behövs.

Denna del är, återigen, ganska mycket kopiera och klistra, men du kommer alltid att vilja ge ditt program ett unikt namn.

// Bygg programmets användargränssnitt _buildUI() {

// Skapa programmets fönster this._window = new Gtk.ApplicationWindow({ application: this.application, window_position: Gtk.WindowPosition.CENTER, border_width: 10, title: "Välkommen till Grid"});

Avslutningsvis kommer vi att börja _buildUI-funktionen genom att skapa ett nytt ApplicationWindow. Vi anger att det följer med detta program, att det ska dyka upp mitt på skärmen och att det bör finnas åtminstone 10 bildpunkter mellan den utvändiga kanten och komponenter inuti. Vi ger det också en titel som kommer att visas i toppen av fönstret.

# Att gräva i GTK+-verktygslådan

Vilka komponenter ska vi använda? Låt säga att vi vill skriva ett program som ser ut så här:

Vi kommer åtminstone att behöva en bild och en textetikett som följer med den. Låt oss börja med bilden:

// Skapa en bild this._image = new Gtk.Image ({ file: "gnome-image.png" });

Du kan hämta bildfilen som används i detta exempel här. Säkerställ att du placerar den i samma katalog som koden du skriver.

// Skapa en etikett this._label = new Gtk.Label ({ label: "Välkommen även till GNOME!" });

Den koden lägger till etiketten nedanför. Du kan se hur vi skapar komponenter här; var och en är en del av Gtk och vi kan ge dem egenskaper som anpassar hur de beter sig. I detta fall sätter vi file-egenskapen för denna Image till att vara filnamnet på bilden vi önskar visa, och Labelns label-egenskap till att vara meningen vi vill ha under bilden.

Ja, det kan låta överflödigt att ha en Label som har en label-egenskap, men det är det inte. Andra komponenter som innehåller text har en label-egenskap, så det är konsekvent att Label-komponenten också har en.

Vi kan dock inte bara lägga till dessa komponenter i vårt fönster i ordning, på samma sätt som HTML-element dyker upp i ordningen du skriver dem. Detta är för att ApplicationWindow endast kan innehålla en komponent.

Hur kommer vi runt det? Genom att skapa en komponent som fungerar som en behållarkomponent, som kan innehålla mer än en komponent och organisera dem inuti. Skåda: Grid (rutnätet).

// Skapa rutnätet this._grid = new Gtk.Grid ();

Vi ger det inga egenskaper än. Dessa kommer senare allt eftersom vi lär oss att använda Grids krafter. Först, låt oss koppla den Image och Label vi skapade till vår Grid.

// Koppla bilden och etiketten till rutnätet this._grid.attach (this._image, 0, 0, 1, 1); this._grid.attach (this._label, 0, 1, 1, 1);

Denna kod ser oerhört komplicerad ut, men det är den inte. Detta är vad numren betyder:

Det första numret är på vilken vänster-till-höger-position som saker ska placeras på, med början på 0. En komponent som använder 0 här, placeras längst till vänster.

Det andra numret är på vilken topp-till-botten-position som en komponent ska placeras på, med början på 0. Labeln ska placeras under bilden, så vi anger 0 för Image och 1 för Label här.

Det tredje och fjärde numret är hur många kolumner och rader en komponent ska uppta. Vi kommer att se hur dessa fungerar om en liten stund.

// Lägg till rutnätet till fönstret this._window.add (this._grid);

// Visa fönstret och alla barnkomponenter this._window.show_all(); }

};

// Kör programmet let app = new WelcomeToTheGrid (); app.application.run (ARGV);

Nu när vi skapat Grid och kopplat alla komponenter till den, så kan vi lägga till den till fönstret och be fönstret att visa sig självt, som det sista steget i _buildUI-funktionen. Som alltid avslutar vi med att skapa en ny instans av programmets klass och be den att köra.

Spara ditt program som welcome_to_the_grid.js. Sedan, för att köra ditt program, öppna en terminal, gå till katalogen där ditt program finns och skriv

     $ gjs welcome_to_the_grid.js

Där har vi det! Men vänta! Det ser inte helt rätt ut. Varför är Labeln upptryckt intill bilden på det där sättet? Det ser inte så fint ut och det gör det svårare att läsa. Vad kan vi göra åt detta?

# Att anpassa Grid

En sak vi kan göra är att ge Labeln en margin_top-egenskap när vi skapar den. Detta fungerar lite grann som att sätta en marginal för ett HTML-element via inbyggda CSS-stilar.

// Skapa en etikett this._label = new Gtk.Label ({ label: "Välkommen även till GNOME!", margin_top: 20 });

Men om vi gör det så kommer vi så klart ifall vi ersätter Labeln med någonting annat — eller lägger in en annan komponent — även behöva upprepa margin_top för denna. Annars kommer det att sluta med att vi har någonting i stil med detta:

Vi skulle kunna ange en margin_bottom-egenskap för Imagen, men det kommer inte att fungera när Labeln är i en separat kolumn. Så hur vore det om vi provade med detta istället:

// Skapa Grid this._grid = new Gtk.Grid ({ row_spacing: 20 });

Detta kommer att göra så att det alltid finns 20 bildpunkter utrymme mellan varje horisontell rad.

Ja, du kan också sätta column_spacing-egenskapen på en Grid, eller margin_left- och margin_right-egenskaper på vilken komponent som helst. Prova dem om du vill!

# Lägga till fler komponenter

Om vi skulle vilja lägga till en andra Label, hur skulle vi då göra så att det faktiskt såg ut som om den passade in? Ett sätt är att centrera Imagen i toppen så att den ligger ovanför båda förekomsterna av Label istället för bara den till vänster. Det är här dessa andra nummer i Grids attach-method kommer in:

// Skapa en andra etikett this._labelTwo = new Gtk.Label ({ label: "The cake is a pie." });

// Koppla bilden och etiketterna till rutnätet this._grid.attach (this._image, 0, 0, 2, 1); this._grid.attach (this._label, 0, 1, 1, 1); this._grid.attach (this._labelTwo, 1, 1, 1, 1);

Efter att vi skapat den andra Labeln, kopplar vi den till Grid till höger om den första Labeln. Kom ihåg att de första två numren räknar kolumner och rader från vänster till höger och från topp till botten, med början på 0. Som om den första Labeln är i kolumn 0 och rad 1, då kan vi placera den andra i kolumn 1 och rad 1 för att placera den till höger om den första Labeln.

Notera numret 2 i attach-uttrycket för Imagen. Det är själva tricket här. Det numret är hur många kolumner Imagen sträcker sig över, eller hur? Så när vi för allting samman får vi någonting i stil med detta:

Det finns två saker som du bör notera här.

Att sätta Imagen till att sträcka sig över två kolumner sträcker inte bilden i sig horisontellt. Istället sträcker det den osynliga rutan som tas upp av Image-komponenten så att den fyller båda kolumnerna, sedan placeras Imagen i mitten på den rutan.

Även om vi har satt Grids row_spacing- och ApplicationWindows border_width-egenskaper så har vi ännu inte satt någonting som skapar en kant mellan de två förekomsterna av Label. De var separata tidigare när Imagen bara var i en kolumn men nu när den sträcker sig över båda så ser GNOME inte en orsak till att hålla isär dem.

Det finns åtminstone tre sätt på vilka vi kan kommer runt det sistnämnda. Först kan vi sätta margin_left eller margin_right på endera av förekomsterna av Label:

För det andra kan vi sätta Grids column_homogeneous-egenskap till true.

// Skapa Grid this._grid = new Gtk.Grid ({ column_homogeneous: true, row_spacing: 20 });

Detta gör att det ser ut något i stil med det här:

Och för det tredje kan vi sätta Grids column_spacing-egenskap på samma sätt som vi satt dess row_spacing.

// Skapa Grid this._grid = new Gtk.Grid ({ column_spacing: 20, row_spacing: 20 });

Detta får det att se ur så här:

# Att använda standardbilder

GNOME har redan massor av standardbilder till hands, som vi kan använda om vi inte känner för att skapa egna eller om vi vill ha en universellt igenkänd ikon. Så här skapar vi en standardbild, jämfört med hur vi skapar en normal bild:

// Skapa en bild this._image = new Gtk.Image ({ file: "gnome-image.png" });

// Skapa en andra bild genom att använda en standardikon this._icon = new Gtk.Image ({ stock: 'gtk-about' });

Efter det kopplar vi det till Grid till vänster om vår första Label. (Vi använder inte den andra i detta exempel.)

// Koppla bilderna och etiketten till rutnätet this._grid.attach (this._image, 0, 0, 2, 1); this._grid.attach (this._icon, 0, 1, 1, 1); this._grid.attach (this._label, 1, 1, 1, 1);

När vi kör det, ger detta oss:

Så ser standardikonen ”Om” ut. Du kan se en lista över alla standardobjekt från gtk-about och framåt i GNOME:s utvecklardokumentation. Den skrevs för C-programmerare, men du måste inte känna till C för att använda den; bara titta på delen inom citationstecken, så som 'gtk-about' och kopiera den delen för att använda ikonen intill.

Vi placerar enkla citattecken runt 'gtk-about' här eftersom den delen aldrig kommer att behöva översättas till ett annat språk (till skillnad från textsträngar som har dubbla citattecken runt sig). Om det faktiskt blev översatt skulle det ha sönder ikonen eftersom dess namn fortfarande är "gtk-about" oavsett vilket språk du talar.

# Vad är nästa steg?

Innan vi går vidare till nästa handledning, låt oss prova något annat:

// Skapa en knapp this._button = new Gtk.Button ({ label: "Välkommen även till GNOME!"});

// Koppla bilderna och knappen till rutnätet this._grid.attach (this._image, 0, 0, 2, 1); this._grid.attach (this._icon, 0, 1, 1, 1); this._grid.attach (this._button, 1, 1, 1, 1);

Det är helt rätt, vi gjorde om Labeln till en Button bara genom att ändras dess namn! Om du kör programmet och klickar på den så kommer du dock att upptäcka att den inte gör något. Hur får vi vår Button att göra något? Det kommer vi att upptäcka i nästa handledning.

Om du vill, spendera lite tid på att experimentera med Grid, Label och Image, inklusive standardbilder.

Ett trick du kan använda för att åstadkomma mer komplexa layouter är att nästla flera Grid inuti varandra. Detta låter dig gruppera relaterade komponenter och enkelt omarrangera dem. Titta på kodexemplet RadioButton om du vill se hur detta görs.

# Fullständigt kodexempel

#!/usr/bin/gjs

imports.gi.versions.Gtk = '3.0'; const Gtk = imports.gi.Gtk;

class WelcomeToTheGrid {

// Skapa programmet i sig constructor() { this.application = new Gtk.Application();

// Anslut ”activate”- och ”startup”-signaler till återanropsfunktionerna this.application.connect('activate', this._onActivate.bind(this)); this.application.connect('startup', this._onStartup.bind(this)); }

// Återanropsfunktion för ”activate”-signal visar fönster när den aktiveras _onActivate() { this._window.present(); }

// Återanropsfunktion för ”startup”-signal bygger användargränssnittet _onStartup() { this._buildUI (); }

// Bygg programmets användargränssnitt _buildUI() {

// Skapa programmets fönster this._window = new Gtk.ApplicationWindow({ application: this.application, window_position: Gtk.WindowPosition.CENTER, border_width: 10, title: "Välkommen till Grid"});

// Skapa Grid this._grid = new Gtk.Grid ({ // column_homogeneous: true, // column_spacing: 20, row_spacing: 20 });

// Skapa en bild this._image = new Gtk.Image ({ file: "gnome-image.png" });

// Skapa en andra bild med hjälp av en standardikon this._icon = new Gtk.Image ({ stock: 'gtk-about' });

// Skapa en etikett this._label = new Gtk.Label ({ label: "Välkommen även till GNOME!", /* margin_top: 20 */ });

/* Skapa en andra etikett this._labelTwo = new Gtk.Label ({ label: "The cake is a pie." }); */

/* Skapa en knapp this._button = new Gtk.Button ({ label: "Välkommen även till GNOME!"}); */

// Anslut bilderna och knappen till rutnätet this._grid.attach (this._image, 0, 0, 2, 1); this._grid.attach (this._icon, 0, 1, 1, 1); this._grid.attach (this._label, 1, 1, 1, 1);

// this._grid.attach (this._label, 0, 1, 1, 1); // this._grid.attach (this._labelTwo, 1, 1, 1, 1);

// this._grid.attach (this._button, 1, 1, 1, 1);

// Lägg till rutnätet till fönstret this._window.add (this._grid);

// Visa fönstret och alla barnkomponenter this._window.show_all(); }

};

// Kör programmet let app = new WelcomeToTheGrid (); app.application.run (ARGV);