Dwa panele w jednym frame

0

W jaki sposób dodać dwa panele do jednego frame'a ?

W poniższy sposób MyPanel3 jest wyświetlany w oknie, ale MyPanel2 już nie - po mimo, że według parametrów setBounds() nie powinny się przesłaniać.

package my;
import javax.swing.*;
import java.awt.*;

public class Main2 {
	public static void main(String args[]) {
		new MyFrame2();
	}

}

class MyFrame2 extends JFrame {

	MyFrame2() {
		setVisible(true);
		setBounds(500, 100, 400, 300);
		setTitle("Lab 1"); 
		
		getContentPane().add(new MyPanel2());
		getContentPane().add(new MyPanel3());
		
	}
}

class MyPanel2 extends JPanel {
	
	MyPanel2() {
		setLayout(null);
	}
	
	public void paintComponent(Graphics gp) {
		super.paintComponent(gp);
		setBounds(250, 10, 100, 100);
		setBackground(Color.darkGray);
		
		repaint();
	}
}

class MyPanel3 extends JPanel {
	
	MyPanel3() {
		setLayout(null);
	}
	
	public void paintComponent(Graphics gp) {
		super.paintComponent(gp);
		setBounds(10, 10, 100, 100);
		setBackground(Color.red);
		
		repaint();
	}
}
0

MyFrame2.setLayout(null)

0

Wtedy nie widać, ani jednego, ani drugiego panelu. Okno jest puste.

0

Teraz nie pojawia się ani jeden ani drugi panel:

package my;
import javax.swing.*;
import java.awt.*;

public class Main2 {
        public static void main(String args[]) {
                new MyFrame2();
        }

}

class MyFrame2 extends JFrame {

        MyFrame2() {
                setLayout(null);
                setVisible(true);
                setBounds(500, 100, 400, 300);
                setTitle("Lab 1"); 
                
                getContentPane().add(new MyPanel2());
                getContentPane().add(new MyPanel3());
                
        }
}

class MyPanel2 extends JPanel {
        
        MyPanel2() {
                setLayout(null);
        }
        
        public void paintComponent(Graphics gp) {
                super.paintComponent(gp);
                setBounds(250, 10, 100, 100);
                setBackground(Color.darkGray);
                
                repaint();
        }
}

class MyPanel3 extends JPanel {
        
        MyPanel3() {
                setLayout(null);
        }
        
        public void paintComponent(Graphics gp) {
                super.paintComponent(gp);
                setBounds(10, 10, 100, 100);
                setBackground(Color.red);
                
                repaint();
        }
}

0
  1. Przenieś setBounds z metody rysującej panel np.: do konstruktora.
  2. Czemu ustawiasz kolor tła w paintComponent ? Metoda setBackground wywoła repaint'a jeżeli kolor tła jest inny niż wcześniej ustawiony dodatkowo ten repaint na końcu (dodaj sobie System.out do paintComponent :p)
  3. Pomyśl nad wykorzystaniem menadżera rozkładu, nawet jeżeli chcesz uzyskać tak statyczny efekt, np:
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Main2 {
        public static void main(String args[]) {
                new MyFrame2();
        }

}

class MyFrame2 extends JFrame {
    
        private JPanel myPanel1;
        private JPanel myPanel2;
        private JPanel myPanel3;
        private JPanel myPanel4;

        MyFrame2() {
                setLayout(new MyLayoutManager().setComponentWidth(150)
                        .setComponentHeight(150).setVerticalGap(10));
                setTitle("Lab 1"); 

                myPanel1 = new JPanel();
                myPanel1.setBackground(Color.RED);
                myPanel2 = new JPanel();
                myPanel2.setBackground(Color.BLACK);
                myPanel3 = new JPanel();
                myPanel3.setBackground(Color.BLUE);
                myPanel4 = new JPanel();
                myPanel4.setBackground(Color.GREEN);
                
                getContentPane().add(myPanel1);
                getContentPane().add(myPanel2);
                getContentPane().add(myPanel3);
                getContentPane().add(myPanel4);
                this.pack();
                setVisible(true);
                this.setLocationRelativeTo(null);
        }
        
      
}

/**
 * Klasa opisująca menadżera mojego rozkładu.
 *
 * @author znikąd
 * @version 0.1.0
 */
class MyLayoutManager implements LayoutManager {
    
    /**
     * Marginesy.
     */
    private Insets margins = new Insets(5, 5, 5, 5);
    
    /**
     * Maksymalna ilość komponentów w jednej lini.
     */
    private int maxInLine = 2;
    
    /**
     * Odległośc między komponentami w poziomie.
     */
    private int horizontalGap = 150;
    
    /**
     * Odległość między komponentami w pionie.
     */
    private int verticalGap = 5;
    
    /**
     * Szerokość komponentu.
     */
    private int width = 100;
    
    /**
     * Wyskokośc komponentu.
     */
    private int height = 100;
    
    /**
     * Rozmiar.
     */
    private final Dimension size = new Dimension(0, 0);
    
    /**
     * Konstruktor.
     */
    public MyLayoutManager() {
    }
    
    /**
     * Konstruktor.
     *
     * @param w szerokość komponentu
     * @param h wysokość komponentu
     */
    public MyLayoutManager(final int w, final int h) {
        this(2, 150, 5, w, h);
    }
    
    /**
     * Konstruktor.
     *
     * @param max maksymalna ilość komponentów w jednej lini
     * @param hGap odległośc miedzy koponentami w poziomie
     * @param vGap odległość między komponetami w pionie
     * @param w szerokość komponentu
     * @param h wysokość komponentu
     */
    public MyLayoutManager(final int max, final int hGap, final int vGap, 
            final int w, final int h) {
        this.maxInLine = max;
        this.horizontalGap = hGap;
        this.verticalGap = vGap;
        this.width = w;
        this.height = h;
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public void addLayoutComponent(final String name, 
            final Component comp) {
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public void layoutContainer(final Container parent) {
        final Component[] components = parent.getComponents();
        int x = this.margins.left;
        int y = this.margins.top;
        int count = 0;
        for (final Component c : components) {
            c.setBounds(x, y, this.width, this.height);
            // dodaje szerokość i odstep w poziomie
            x += this.width + this.horizontalGap;
            count ++;
            if (count == this.maxInLine) {
                count = 0;
                x = this.margins.left;
                y += this.height + this.verticalGap;
            }
        }
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public Dimension minimumLayoutSize(final Container parent) {
        this.calculateSize(parent);
        return this.size;
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public Dimension preferredLayoutSize(final Container parent) {
        this.calculateSize(parent);
        return this.size;
    }
    
    private final void calculateSize(final Container parent) {
        // wyciągam komponenty
        final Component[] components = parent.getComponents();
        
        int layoutWidth = this.margins.left + this.margins.right;
        int layoutHeight = this.margins.top + this.margins.bottom;
        // obliczam szerokość
        if (components.length >= this.maxInLine && this.maxInLine > 0) {
            layoutWidth += (this.maxInLine * this.width) + 
                ((this.maxInLine - 1) * this.horizontalGap);
        } else if (components.length > 0 && this.maxInLine > 0) {
            layoutWidth += (components.length * this.width) + 
            ((components.length - 1) * this.horizontalGap);
        }
        // obliczam wysokość
        int rows = components.length / this.maxInLine;
        if (rows > 0) {
            layoutHeight += (rows * this.height) + ((rows -1) * this.verticalGap);
        }
        this.size.setSize(layoutWidth, layoutHeight);
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public void removeLayoutComponent(final Component comp) {
    }

    /**
     * Zwraca odstep między komponentami w poziomie.
     *
     * @return odstep między komponentami w poziomie
     */
    public final int getHorizontalGap() {
        return this.horizontalGap;
    }

    /**
     * Ustawia odstep między komponentami w poziomie.
     *
     * @param hGap odstep między komponentami w poziomie
     * @return instancja klasy
     */
    public final MyLayoutManager setHorizontalGap(final int hGap) {
        this.horizontalGap = hGap;
        return this;
    }

    /**
     * Zwraca odstęp między komponentami w pionie.
     *
     * @return odstęp między komponentami w pionie
     */
    public final int getVerticalGap() {
        return this.verticalGap;
    }

    /**
     * Ustawia odstęp między komponentami w pionie.
     *
     * @param vGap odstęp między komponentami w pionie
     * @return instancja klasy
     */
    public final MyLayoutManager setVerticalGap(final int vGap) {
        this.verticalGap = vGap;
        return this;
    }

    /**
     * Zwraca szerokośc komponentu.
     *
     * @return szerokośc komponentu
     */
    public final int getComponentWidth() {
        return this.width;
    }

    /**
     * Ustawia szerokośc komponentu.
     *
     * @param w szerokośc komponentu
     * @return instancja klasy
     */
    public final MyLayoutManager setComponentWidth(final int w) {
        this.width = w;
        return this;
    }

    /**
     * Zwraca wysokość komponentu.
     *
     * @return wysokość komponentu
     */
    public final int getComponentHeight() {
        return this.height;
    }

    /**
     * Ustawia wysokość komponentu.
     *
     * @param h wysokość komponentu
     * @return instancja klasy
     */
    public final MyLayoutManager setComponentHeight(final int h) {
        this.height = h;
        return this;
    }

    /**
     * Zwraca marginesy.
     *
     * @return marginesy
     */
    public final Insets getMargins() {
        return this.margins;
    }
}
0

Użyj LayoutManagera, np. BorderLayout

class MyFrame2 extends JFrame {

        MyFrame2() {
                setLayout(new BorderLayout()); //BorderLayout
              
                setBounds(500, 100, 400, 300);
                setTitle("Lab 1");
               
                getContentPane().add(new MyPanel2(), BorderLayout.EAST); // PRAWO
                getContentPane().add(new MyPanel3(), BorderLayout.WEST); // LEWO
                
                setVisible(true); //to powinno być na końcu    
        }
}
0

Dzięki za rady powyżej, były bardzo pomocne ;-) Ale ...

znikąd napisał(a)

Czemu ustawiasz kolor tła w paintComponent ?

Czyli ustawianie tła w paintComponent jest mało eleganckie ?

Tak więc dlaczego, gdy zdefiniuje metodę paintComponent(), to metoda setBacground() w konstruktorze w ogóle nie działa ? Za to wystarczy, że usunę paintComponent i faktycznie kolor zielony się pojawia ..

	class Panel extends JPanel {
		
		Panel() {
			setBackground(Color.green);			
		}
		
		public void paintComponent(Graphics g) {
		            ...			
		} 
	}
0
puertainsua napisał(a)

Tak więc dlaczego, gdy zdefiniuje metodę paintComponent(), to metoda setBacground() w konstruktorze w ogóle nie działa ? Za to wystarczy, że usunę paintComponent i faktycznie kolor zielony się pojawia ..

Nie dziala (w zasadzie to dziala, tyle ze jej zadaniem jest jedynie zapisanie w obiekcie informacji o kolorze tla, a nie jak zakladales, nalozenie koloru na panel), gdyz wlasnie przesloniles metode klasy bazowej JPanel, ktora byla odpowiedzialna m.in. za wypelnienie kolorem tla komponentu.
Od tej chwili jest to Twoje zadanie do zrealizowania.
Mozesz albo na wlasna reke zrealizowac wszystkie/wybrane zadania, ktore realizowala przeslaniana metoda, albo wywolac w niej metode klasy bazowej:

super.paintComponent(g);

i dodatkowo zrealizowac to co chciales zrobic przeslaniajac te metode

0

Już rozumiem, dzięki za wyjaśnienie.

Jeszcze jedna kwestia. Chcę, żeby po kliknięciu na JButton, w innym panelu wykonała się jakaś akcja, niech będzie to na przykład narysowanie prostej przy użycie drawLine(). No właśnie ... tylko jak to zaplanować efektywnie ?

Moje rozwiązanie problemu jest takie:

	class PanelPrawy extends JPanel {
		Graphics2D g2d;
                        boolean czyNarysowac;
		
		PanelPrawy() {
			...
		}
		
		public void paintComponent(Graphics g) {
			
			...
                        if (czyNarysowac) {     
			g2d.drawLine(0, 0, pPrawy.getWidth(), pPrawy.getHeight());
                        }
		} 
	}

No i metoda, którą dołączam do przycisku:

			public void actionPerformed(ActionEvent e) {
				
				czyNarysowac = true; repaint();
		    }

Czyli streszczając, po kliknięciu na przycisk zmienna czyNarysowac jest ustawiana na true, a wtedy prosta jest rysowana. Niby działa, ale to chyba kiepska strategia ? Jak to powinno wyglądać porządnie ?

0

Moim zdaniem to jest prawidłowa strategia. GUI powinno wpływać na stan zmiennych programu, na podstawie których program działa (również coś wyrysowuje itp.). Błędem byłoby właśnie coś odwrotnego czyli wykonywanie bezpośrednio jakichś akcji z odbiorców zdarzeń takich jak actionPerformed(). A już tym bardziej akcji długotrwałych, co spowodowałoby zawieszenie GUI. Do wykonania czasochłonnych zadań powinieneś użyć innych wątków niż EventDispatchThread, który "porusza" kodem wszystkich kontrolek oraz odbiorców zdarzeń - np. SwingWorkera. Ale nawet wtedy działania w innym wątku powinny zmieniać jedynie stan zmiennych programu (tu uwaga na wielowątkowy dostęp do tych danych), na podstawie których metody paint i paintComponent wyrysowują coś na ekranie.

U Ciebie zmienna "czyNarysować" jest stanem programu, który zmieniasz bez użycia wątków ponieważ zmiana ta nie wymaga obliczeń więc jest błyskawiczna. Rysowanie (i reszta GUI) korzysta m.in. z tej danej. Jest OK.

0

Może powiem o co mi dokładnie chodzi i dlaczego powyższy sposób się nie przyjął.

Na laborce miałem za zadanie zrobić coś takiego, że klikając na JButton, na innym panelu miało rysować się koło w trybie XOR. Operując na wartości boolean tak jak powyżej, wydawało się że działa dobrze, ale jednak prowadzący oczekiwał czegoś innego ... przy moim sposobie nie ważne, czy kliknelibyśmy na JButton "Rysuj" 1 raz, 5 razy, czy 100 - nie było już żadnej dodatkowej akcji, bo przecież wartość boolean była już ustawiona na true. Prowadzący oczekiwał, że wykorzystam charakterystyke XOR... czyli 1 klikniecie pojawia sie koło, 2 klikniecie znika, 3 klikniecie znowu sie pojawia itd. Możnaby zastosować sztuczke w stylu manipulowania zmienną boolean, ale to przecież nie o to chodzi i to raczej ominięcie problemu. Można też zrobić, żeby w actionPerformed() po kazdym kliknieciu pakować nowe Koło do tablicy, a w paintComponent() w petli foreach po prostu rysowac te kola z tablicy, dzieki czemu ta wlasciwosc znikania i pojawiania sie bylaby chyba zachowana. Ale to tez takie siermiezne i chyba mozna prosciej ?

idealnie byloby odwolywac sie w metodzie actionPerformed() do obiektu Graphics2d i nim rysowacc cos w tym tylu:

		class rysujKolo implements ActionListener {
			public void actionPerformed(ActionEvent e) {
				
				pPrawy.g2d.setXORMode(Color.white);
				pPrawy.g2d.fillOval((int)(pPrawy.getWidth()/2)-35, (int)(pPrawy.getHeight()/2)-35, 70, 70);
				pPrawy.repaint();
				
		    }	
		}

no ale cos takiego to chyba niemozliwe do zrealizowania bo w ogole nie ingerujemy w metode paintComponent() ..

0

Może tak:

public void actionPerformed(ActionEvent e) 
{                               
       czyNarysowac = !czyNarysować; 
       repaint();
}

a w funkcji paintComponent rysujesz zawsze koło, kolor uzależniasz od zmiennej czyNarysować: wybrany kolor koła albo kolor tła.

0

Odrysowywanie w trybie XOR działa świetnie, ale nie koniecznie jest sensowne w takim zastosowaniu w jakim prowadzący chciałby je widzieć. To działa tylko pod warunkiem, że aplikacja ma pełną kontrolę nad tym co odrysowuje i kiedy (jest tak tylko gdy robimy program na pełny ekran). Swing jeszcze jakiś czas temu nie dawał takiej kontroli. Procedura paint lub każda z tej rodziny może zostać uruchomiona w każdym momencie w którym ekran zostanie zniszczony, nadpisany, a czasem pozornie zupełnie bez powodu (gdy pulpit działa w trybie wirtualnych ekranów). W efekcie można to rozwiązać przez liczenie każdego wywołania paint i sprawdzania jego parzystości tak aby nie doszło do sytuacji w której aplikacja zakłada iż rysuje podczas gdy naprawdę maże i odwrotnie.
Tak więc wystarczy jedno paint() z kółkiem, które zawsze jest odrysowywane w trybie XOR. Jednak aby to przypadkowe odrysowywania nie wpłynęły na obraz trzeba wyłączyć odrysowywania wymuszane przez system za pomocą Component.setIgnoreRepaint(true). Wtedy każde actionPerformed() z przycisku wymusi kolejne odrysowanie i zabawę z pojawianiem się i znikaniem kółka. Jeszcze ciekawiej byłoby przed odrysowaniem kółka wrzucić odrysowywanie (już bez XOR) jakiegoś stałego obrazka, żeby było widać że po skasowaniu kółka zawartość obrazu jest nienaruszona.
Jednak po użyciu setIgnoreRepaint() komponent przestaje być odrysowywany wtedy gdy coś go przysłoni i odsłoni, więc ma to sens gdy komponent jest odrysowywany automatycznie co jakiś okres czasu (np. animacja). W przeciwnym wypadku takim jak asynchroniczne wciskanie przycisku każde zepsucie obrazu niszczy go aż do kolejnego naciśnięcia przycisku. Tak źle i tak niedobrze. Dlatego napisałem, że XOR nadaje się głównie do aplikacji pełnoekranowych.

1 użytkowników online, w tym zalogowanych: 0, gości: 1