[Swing] Posizionare i componenti

di il
9 risposte

[Swing] Posizionare i componenti

Salve,

Sto leggendo vari tutorials su come posizionare i componenti una volta creato JFrame e JPanel.
Non ho capito sinceramente come posizionarli a piacere.
Ho letto che sono disponibili vari layout , in questo caso sto utilizzando GridBagLayout
mentre quello che ho utilizzato in precedenza, flowLayout , sembra solo utile per posizionare tutto su un riga.
Nel codice seguente ho lasciato commenti sperando in un vostro riscontro.
Mi aspetto di posizionare i componenti con c.gridx e c.gridy, ma non ho capito perchè i componenti
si presentano inizialmente al centro della finestra quando i valori di cui sopra sono pari a zero


package window;

import java.io.*;
import java.util.Scanner;
import java.awt.*;
import javax.swing.*;

public class window {
		 
	private static void createWindow(){
		
		JFrame window = new JFrame ("Secret Garden");		//classe per creare la finestra con titolo
		JPanel panel = new JPanel(new GridBagLayout());		//classe per creare un pannello con all'interno i componenti(controlli)
		GridBagConstraints c = new GridBagConstraints();	//Uno dei tipi di posizionamento , in questo caso è come una griglia
		
		c.anchor = GridBagConstraints.WEST;					//l'anchor di ogni oggetto, in questo caso a sinistra(?)
		c.gridx=0;											//coordinata x all'interno della griglia
		c.gridy=0;											//coordinata y all'interno della griglia
		c.insets =  new Insets(0,0,0,0);					//determina il valore in pixel pari alla distanza tra un componente ed un altro
		
		JLabel mapTitle = new JLabel("map.xml");			//classe per creare una label
		JLabel mapBody = new JLabel("vuoto");				// un'altra label
		
		panel.add(mapTitle,c);								//aggiungo al pannello la label
		c.gridy++;											// mi sposto di un punto sulla griglia in verticale
		panel.add(mapBody,c);								//aggiungo al paennllo un'altra label

		window.setDefaultCloseOperation(window.EXIT_ON_CLOSE);
		window.setLocationRelativeTo(null);
		window.add(panel);									//aggiungo il pannello con tutti i componenti all'interno alla finestra corrente
		window.setSize(300,300);
		window.setVisible(true);
	}
	
	public static void main(String[] args)
	{
		createWindow();
	}
} 

9 Risposte

  • Re: [Swing] Posizionare i componenti

    Cyrano ha scritto:


    Non ho capito sinceramente come posizionarli a piacere.
    Posizionarli a piacere, ovvero il posizionamento "assoluto", è possibile. Basta togliere il layout-manager da un contenitore e poi impostare posizione E dimensione di tutti i componenti nel contenitore usando su ciascuno setBounds o la coppia setLocation/setSize.

    Ma il posizionamento assoluto è sconsigliabile, se non per casi davvero ultra-specialistici.
    Perché:
    a) la interfaccia diventa non ridimensionabile di per sé, a meno che TU scrivi un mucchio di codice per fornire una sorta di logica di ridimensionamento (che probabilmente verrebbe lunga, fumosa ed error-prone).
    b) la interfaccia non si adatta facilmente a Look&Feel differenti, a meno di andare ad impostare anche qui un po' di cose (L&F, font, ecc..)

    Cyrano ha scritto:


    Ho letto che sono disponibili vari layout , in questo caso sto utilizzando GridBagLayout
    mentre quello che ho utilizzato in precedenza, flowLayout , sembra solo utile per posizionare tutto su un riga.
    Escludendo un momento GroupLayout e SpringLayout (due l.m. parecchio complessi, che generalmente NON si usano scrivendo codice "a mano"), difficilmente una interfaccia utente si può fare con un solo l.m. In AWT/Swing l'obiettivo (e anche la "abilità" del programmatore) dovrebbe essere quello di sfruttare eventualmente più l.m. andando ad "inscatolare" componenti, dentro contenitori, dentro altri contenitori, ecc...

    Cyrano ha scritto:


    non ho capito perchè i componenti si presentano inizialmente al centro della finestra
    Bisogna anche dare un "peso" ai componenti, i constraint weightx/weighty
  • Re: [Swing] Posizionare i componenti

    Salve,

    Ho letto subito la tua risposta ed ho poi deciso di installare NetBeans.
    C'è scritto chiaramente nella guida ufficiale che il "GUI Building" aiuta o meglio permette di posizionare i componenti senza conoscere
    il layout manager, ritenuto complicato dalla stessa guida.
    Io a piccoli passi vorrei continuare con NetBeans, temo solo di non perdere una parte importante
    Secondo te mi conviene proseguire con NetBeans ?
  • Re: [Swing] Posizionare i componenti

    Io personalmente ti consiglierei di realizzare a mano le tue interfacce.

    Non sono un esperto di swing, ma sono circa due anni che mi diverto a realizzare interfacce grafiche per piccole applicazioni/giochini, e posso dire di avere imparato moltissimo.
    Temo che un editor visuale, oltre a generare codice poco pulito e difficilmente modificabile in seguito, ti tolga grandi possibilità di apprendimento.

    Come dice andbin è importante imparare ad usare e combinare diversi Layout Manager, che presi singolarmente possono essere davvero semplici: ti assicuro che combinando FlowLayout, GridLayout, BorderLayout e BoxLayout hai già un'infinità di possibilità per costruire una vastissima gamma di interfacce grafiche, senza andare a scomodare layout manager più complessi.
    Poi se la cosa ti appassiona e vuoi impararne di più elaborati (GridBagLayout in primis) ben venga, ma io inizierei dalle basi
  • Re: [Swing] Posizionare i componenti

    Piacerebbe anche a me fare tutto a mano per poter comprendere il dietro le quinte, ho quindi riprovato:
    Ho capito che è possibile aggiungere più di un container all'interno di un frame e quindi successivamente decidere quali o quanti layouts aggiungere per il nostro scopo.
    Ora io vorrei solo dividere il frame in questo modo :


    Ho provato ad occuparmi prima di Label1 e TextField1:
    1) Creato frame.
    2) Ottenuto riferimento al container.
    3) Creato i componenti Label1 e TextField1.
    4) Settato layout con GridLayout.
    5) Lasciato perdere per il momento Label2.

    Ho ottenuto che Label1 rimane sopra a Texfield1 ma lo spazio viene tutto diviso perfettamente per 2 e quindi non come nell'immagine
    Ora mi chiedo :
    1) Come cambio le dimensioni di Label1 e TextField1 ?
    2) Per aggiungere Label2, visto che occupa tutto "lo spazio sull'asse delle y" , devo aggiungere un altro container (JPanel) per renderlo
    indipendente dal layout precedente ?
    
    public static void main(String[] args) {
           
            JFrame jFrame = new JFrame ("Prova frame"); // Finestra principale
            jFrame.setSize(400, 400); // di dimensioni 400x400
            Container c1 = jFrame.getContentPane(); //Container principale
            
            c1.add(new JLabel("Label"));
            c1.add(new JTextField ("TextField"));
            c1.setLayout(new GridLayout(0,1));
            
            jFrame.setDefaultCloseOperation(jFrame.DISPOSE_ON_CLOSE);
            jFrame.setLocationRelativeTo(null);
    	jFrame.pack();
            jFrame.setVisible(true);
        }
    
  • Re: [Swing] Posizionare i componenti

    Le dimensioni della label e del textfield non dovresti cambiarle a mano, in realtà di solito è sconsigliabile l'utilizzo di metodi come setSize (questo in particolare, anche per il frame utilizza il metodo pack () e lascia perdere il setSize) o i vari setPreferredSize (), setMaximumSize (), setMinimumSize ().
    Talvolta sarai costretto a usarli (ad esempio se fai "custom painting" su un pannello, a volte sarai costretto a forzare la sua dimensione), ma il più delle volte non serve, e queste istruzioni sono delle forzature che spesso i layout manager ignorano.

    Lo spazio viene diviso perfettamente in due perché quello è il comportamento del GridLayout : ogni cella occupa lo stesso spazio in verticale e in orizzontale, e lo spazio viene calcolato come lo spazio "massimo" richiesto. Quindi se una cella contiene un componente sciolto, questo sarà "tirato" per occupare lo spazio intero.

    In questo caso quindi il GridLayout non è quello di cui hai bisogno. Ma ti sarà molto utile in futuro: a volte vuoi dividere lo spazio tra due o più pannelli in misura uguale, ma non vuoi allungare i componenti.
    In quel caso il trucco è avere un contenitore interno alla cella (come un JPanel) e inscatolare il componente all'interno del pannello, in modo che l'allineamento funzioni ma il componente mantenga la propria dimensione.

    Il disegno che hai postato è il tipico utilizzo di un BorderLayout, ma ci sono alcune differenze.
    Nell'allegato in basso c'è un'immagine di come vengono posizionati i componenti in un BorderLayout (nota che non tutte le aree devono avere componenti all'interno, puoi tranquillamente avere solo nord centro e sud ad esempio).

    Nel tuo caso avresti bisogno di tre aree ma se utilizzassi ad esempio il BorderLayout.CENTER per la Label 1, il BorderLayout.EAST per la Label 2 e il BorderLayout.SOUTH per il Textfield 1, il textfield occuperebbe anche l'area "al di sotto" della Label2.
    Il BorderLayout.SOUTH infatti occupa l'intera lunghezza del proprio contenitore.

    Se vuoi evitare questo comportamento hai molte strade: ad esempio nella parte SOUTH potresti inserire un pannello, a cui setti ad esempio un FlowLayout (con allineamento a sinistra). A questo pannello aggiungi il textfield, in modo che stia allineato a sinistra del pannello senza andare sotto a label2.
    In questo modo però TextField1 non si allargherà all'allargarsi della finestra, ma sarà sempre nell'angolo in basso a sinistra.

    La via corretta per replicare l'interfaccia del tuo disegno è però un'altra: innanzitutto assegni come layout del content pane un BorderLayout.
    Nella parte centrale (ricorda che BorderLayout.CENTER occupa tutto lo spazio "in eccesso" del proprio contenitore) inserirai un pannello (chiamiamolo panel1 per ora) che conterrà in seguito Label1 e TextField1. Nella parte a destra inserirai Label2, che essendo in BorderLayout.EAST occuperà tutto lo spazio verticale dell'interfaccia, perché le parti BorderLayout.NORTH e BorderLayout.SOUTH del content pane non vengono utilizzate.
    A questo punto panel1 (che sta in BorderLayout.CENTER del tuo content pane) sarà ulteriormente suddiviso in due parti.
    E' conveniente settare anche a panel1 un BorderLayout, e aggiungere il TextField1 nella parte SOUTH, mentre la Label1 starà nella parte CENTER per occupare tutto lo spazio rimanente in verticale di panel1.

    In allegato trovi anche un'immagine di come risulterebbe l'interfaccia con questa disposizione, che dovrebbe replicare la tua immagine.
    Forse spiegato in questo modo il procedimento di composizione dei pannelli ti potrebbe sembrare un po' complesso, ma è davvero più facile a farsi che a dirsi, basta fare un po' di esperienza
    Allegati:
    18925_deed393dab3126f2ae28fb57db06a722.png
    18925_deed393dab3126f2ae28fb57db06a722.png

    18925_ab0e5fc71a5f0d48653bc8987b94f10d.jpg
    18925_ab0e5fc71a5f0d48653bc8987b94f10d.jpg
  • Re: [Swing] Posizionare i componenti

    Sera,

    Ho provato a seguire il tuo consiglio , ecco il risultato


    
    public static void main(String[] args) {
            // TODO code application logic here
            
            JFrame jFrame = new JFrame ("Prova frame"); // Finestra principale
            jFrame.setSize(400, 400); // di dimensioni 400x400
            Container c1 = jFrame.getContentPane(); //Container principale
           
            BorderLayout layout = new BorderLayout(); // Creo un nuovo oggetto BorderLayout
            layout.setHgap(15); //Imposto la distanza H in pixel fra i componenti
            layout.setVgap(15); //Imposto la distanza V in pixel fra i componenti
            
            JPanel panel = new JPanel(); // creo un oggetto JPanel
            //c1.setLayout(layout);
            panel.setLayout(layout); //Assegno al Pane il layout
                  
            JLabel Label1 = new JLabel("world");
            JLabel Label2 = new JLabel("map");
            JTextField TextField1 = new JTextField("->");
                    
            panel.add(Label2,BorderLayout.WEST);
            c1.add(Label1,BorderLayout.EAST);
            panel.add(TextField1,BorderLayout.SOUTH);
            c1.add(panel);
    
            jFrame.setDefaultCloseOperation(jFrame.DISPOSE_ON_CLOSE);
            jFrame.setLocationRelativeTo(null);
    	//jFrame.pack();
            jFrame.setVisible(true);
        }
    
    Se ho capito bene , Label2 andrà ad occupare tutto lo spazio in verticale perchè non fa parte di panel ?
    Nella parte a destra inserirai Label2, che essendo in BorderLayout.EAST occuperà tutto lo spazio verticale dell'interfaccia, perché le parti BorderLayout.NORTH e BorderLayout.SOUTH del content pane non vengono utilizzate.
    A questo punto panel1 (che sta in BorderLayout.CENTER del tuo content pane) sarà ulteriormente suddiviso in due parti.
    Non ho capito perchè non vengono utilizzate
    C'e modo di vedere i bordi delle label ? per info :Sostituendo le label con dei pulsanti in quanto hanno i bordi visibili ho modo di vedere anche il gap
    Grazie hai già dato una lunga risposta il che non è scontato su un forum


    Edit:
    Per i bordi...Basta guardare tutte le proprietà dei singoli oggetti

    Se hai ancora pazienza , come faccio a evitare che label 2 cambi dimensioni quando il testo all'interno non può essere contenuto su una riga?
  • Re: [Swing] Posizionare i componenti

    Cyrano ha scritto:


    C'e modo di vedere i bordi delle label ? per info :Sostituendo le label con dei pulsanti in quanto hanno i bordi visibili ho modo di vedere anche il gap

    Edit:
    Per i bordi...Basta guardare tutte le proprietà dei singoli oggetti
    Tutti i componenti Swing hanno la nozione di "bordo" tramite un oggetto Border (set/getBorder). Certi componenti Swing non hanno un bordo preimpostato (es. le label) mentre altri ne hanno uno già predefinito (es. pulsanti, textfield).

    Cyrano ha scritto:


    come faccio a evitare che label 2 cambi dimensioni quando il testo all'interno non può essere contenuto su una riga?
    Il testo in JLabel di norma NON può andare a capo. Le label tipicamente sono di una sola riga. Se vuoi che il testo possa andare a capo, c'è un solo modo: sfruttare il rendering HTML dei componenti Swing. Ma devi anche imporre un "preferred size" alla label.

    Prova questo:
    import java.awt.*;
    import javax.swing.*;
    
    public class ProvaFrame extends JFrame {
        public ProvaFrame() {
            super("Prova");
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setSize(300, 300);
    
            JLabel lab = new JLabel("Questo e' un testo abbastanza lungo per prova");
            lab.setPreferredSize(new Dimension(100, 0));
            add(lab, BorderLayout.EAST);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    new ProvaFrame().setVisible(true);
                }
            });
        }
    }
    Vedrai che il testo NON va a capo ed è invece "troncato" con il ellipsis " ... "
    Se vuoi attivare il rendering HTML, il testo deve iniziare con <html>

    new JLabel("<html>Questo ........

    Prova con questa modifica e vedrai che il testo va a capo.


    P.S. ho imposto new Dimension(100, 0) dove lo 0 non viene considerato, perché JLabel è in BorderLayout.EAST.
  • Re: [Swing] Posizionare i componenti

    Ti ha già detto quasi tutto andbin, per quanto riguarda la label "world" (quindi Label1, non Label2), occupa tutto lo spazio in verticale perché è in BorderLayout.EAST e in BorderLayout.NORTH e BorderLayout.SOUTH non ci sono componenti.
    Se una parte nel BorderLayout non ha componenti, semplicemente non viene visualizzata (ma ricorda che tutto lo spazio in eccesso finisce in CENTER), quindi se hai solo CENTER e EAST queste parti saranno affiancate orizzontalmente (riguarda l'immagine sopra) ed entrambe occuperanno l'intera altezza del contenitore (anche panel dunque, che sta in BorderLayout.CENTER).

    Poi panel è a sua volta suddiviso in due parti, tu hai utilizzato WEST e SOUTH. Il tuo textfield, che è in SOUTH, occupa l'intera lunghezza di panel, mentre Label2, che è in WEST, sarà ovviamente al di sopra di SOUTH ma non occuperà l'intera l'intera lunghezza di panel.
    Come dicevo infatti, lo spazio in eccesso finisce in ogni caso in CENTER, quindi la label occuperà solo lo spazio necessario alla sinistra di panel, tutto lo spazio in eccesso non avrà nulla all'interno.

    Un modo semplice per verificare quale/quanto spazio occupano i componenti (soprattutto nei tuoi primi programmi) può essere assegnare un colore diverso ad ogni componente "interessante", oltre a dare loro un bordo.
    Puoi usare il metodo setBackground (Color c) che, essendo un metodo di Component (setBorder è invece definito in JComponent, quindi non viene ereditato dalle classi in java.awt come Container), viene ereditato praticamente da ogni componente grafico.

    Se assegni un colore ad ogni label (piccolo inciso: un componente per poter mostrare correttamente il proprio background deve essere reso opaco, con setOpaque (true). JPanel è già opaco di default, quindi non devi fare nulla, per le JLabel hai bisogno di impostare la proprietà manualmente, per altri componenti (come JButton) la cosa è ancora più delicata perché possono incidere il LookAndFeel e proprietà tipiche del componente, nel caso dei bottoni ad esempio può dare "fastidio" il contentAreaFilled...) vedrai che in effetti map occupa solo la parte sinistra di panel, anche se in BorderLayout.CENTER e BorderLayout.EAST di panel non ci sono componenti.

    PS: è buonissima cosa utilizzare le convenzioni del linguaggio java nel codice che posti, in particolare le variabili (come Label1, Label2 ...) dovrebbero avere il nome che inizia per minuscola.
    Allegati:
    18925_cf8414434ebad6aa46bfba515d8f700d.png
    18925_cf8414434ebad6aa46bfba515d8f700d.png
  • Re: [Swing] Posizionare i componenti

    Buonasera,

    Credo di aver provato un pò tutto quello che mi avete suggerito , che dire c'e molto da studiare e si fa presto a passare da un argomento ad
    un altro in pochi click.
    Alla fine ho fatto questo ( i colori sono un pò discutibili )

    Grazie mille
    
        String leftMsg = "<html> Welcome <br> Player 1 </br></html>";   
        String rightMsg = "<html> Player 2 <br> offline </br></html>";
        
            
        JFrame window = new JFrame ("Lan Battleship");
        window.setSize(800,600);
        Container container = window.getContentPane();
        BorderLayout BorderLayout = new BorderLayout();
            
        JPanel panel = new JPanel();
        
        panel.setLayout(BorderLayout);
        panel.setBackground(Color.darkGray);
        world.setText(leftMsg);
        world.setBorder(BorderFactory.createLineBorder(Color.yellow));
        world.setPreferredSize(new Dimension (300,180));
        world.setForeground(Color.white);
        
        JLabel map = new JLabel (rightMsg);
        
        map.setBorder(BorderFactory.createLineBorder(Color.yellow));
        map.setPreferredSize(new Dimension (300,0));
        map.setOpaque(true);
        map.setBackground(Color.black);
        map.setForeground(Color.white);
        
        JTextField cmd = new JTextField();
        cmd.setBackground(Color.lightGray);
    //    cmd.addKeyListener(new keyListener());
        
        panel.add(world,BorderLayout.WEST);
        panel.add(cmd,BorderLayout.SOUTH);
        panel.setBorder(BorderFactory.createLineBorder(Color.red));
        container.add(map,BorderLayout.EAST);
        container.add(panel);
        
        window.setDefaultCloseOperation(window.DISPOSE_ON_CLOSE);
        window.setLocationRelativeTo(null);
        window.pack();
        window.setVisible(true);
        }
    }
    
Devi accedere o registrarti per scrivere nel forum
9 risposte