Wymiana komponentu w JFrame

0

Testowałem wymianę komponentu wyświetlanego w oknie typu JFrame i trafiłem na niezrozumiałe dla mnie zachowanie programu. Wymieniane komponenty były z klasy Zmieniany Panel:

class ZmiennyPanel extends JPanel
{
    private JLabel l;
    //------------------------
    public ZmiennyPanel(int i)
    {
        super();
        setLayout(new FlowLayout(FlowLayout.CENTER));
        l=new JLabel("Panel nr "+i,JLabel.CENTER);
        add(l);
    }
    //------------------------
    public String getText()
    {
        return l.getText();
    }
}

Konstruktor klasy głównej zawiera taki fragment:

        panele=new ZmiennyPanel[ILE_PANELI];
        for(int i=0;i<ILE_PANELI;i++)
        {
            panele[i]=new ZmiennyPanel(i);
        }
        setLayout(new BorderLayout(2,2));
        getContentPane().add(panele[indeks],BorderLayout.CENTER);

Obsługa przycisków zmieniających panel wygląda tak:

            String cmd=e.getActionCommand();
            getContentPane().remove(panele[indeks]);
            if(cmd.equals("Następny panel"))
            {
                indeks++;
            }
            if(cmd.equals("Poprzedni panel"))
            {
                indeks--;
            }
            if(cmd.equals("Pierwszy panel"))
            {
                indeks=0;
            }
            if(cmd.equals("Ostatni panel"))
            {
                indeks=ILE_PANELI-1;
            }
            if(indeks<0)
            {
                indeks=ILE_PANELI-1;
            }
            indeks=indeks%ILE_PANELI;
            getContentPane().add(panele[indeks],BorderLayout.CENTER);
            System.out.println(panele[indeks].getText());  //**
            getContentPane().invalidate();
            getContentPane().validate();

Komunikaty na konsoli (wiersz(**) są zgodne z oczekiwaniami, napisy w oknie są zagadkowe - żaden napis nie powraca. Jeżeli na starcie programu ILE_PANELI=8, indeks=3, to napisy są takie:
po starcie programu Panel nr 3
po "Następny panel" Panel nr 4
po "Pierwszy panel" Panel nr 0
po "Następny panel" Panel nr 1
po "Następny panel" Panel nr 2
po "Następny panel" Panel nr 2 ???
po "Następny panel" Panel nr 2 ???
po "Następny panel" Panel nr 5
Może mi ktoś to wyjaśnić?

0

Metoda add(), którą dodajesz panele nie dodaje do kontenera obiektów tych, które już w nim istnieją. Tzn. Operacja taka jest pusta i nic nie robi. Tym samym nie przenosi takiego okienka na ostatnia pozycję w kolejności Z-A w jakiej komponenty są wyświetlane. Program jest błędny w tym sensie, że zmiana panelu powinna zmieniać kolejność komponentów, z których wszystkie powinny być w kontenerze. Zamiast tego dodaje tylko nieistniejące w nim wcześniej komponenty i w efekcie raz zmienia tę kolejność, a innym razem nie.

Lecąc po kolei:
Komponent 3 zostaje wrzucony do contentPane w miejscu obsługi, ale nic nie robi ponieważ wcześniej został dodany w konstruktorze. Jednak tego nie widać ponieważ jest on jedynym komponentem, więc wszystko niby gra. Następnie do kontenera zostają wrzucone komponenty 4, 0, 1 i 2. Ponieważ są one nowe dostają ostatnie miejsce, więc są wyświetlane (w układzie BorderLayout tylko ostatni element jest wyświetlany). Następnie dodawany jest element nr 3, który już w nim istnieje, więc kolejność nie zmienia się, a ostatni wciąż jest nr 2. Podobnie przy elemencie nr 4, który również jest olewany bo już istnieje, a element nr 2 jest wciąż ostatni, więc validate() wyświetla go "na wierzchu" przykrywając wszystkie inne. Sprawa zmienia się przy elemencie nr 5, którego nie było, jest on dodawany, a tym samym dostaje ostatnie miejsce na liście. W czego rezultacie staje się widoczny.

Howgh.

0

Dzięki, ale wydaje mi się że zignorowałeś wiersz

    getContentPane().remove(panele[indeks]);

wywołanie

getContentPane().getComponents().length;

po tym wierszu zwraca wartość o jeden mniejszą niż przed nim, więc coś zostało jednak wyrzucone.
Ponadto, jeżeli w tym miejscu

po "Następny panel" Panel nr 2 ???
zmienię rozmiar okna, to napis się magicznie zmieni na właściwy. Podsunęło mi to myśl, bo po validate() dodać repaint(), i zaczęło działać zgodnie z oczekiwaniami.

0

Rzeczywiście. Muszę się temu przyjrzeć jeszcze raz.

Sprawdź czy tego samego efektu nie daje validateTree() zamiast validate().

Z tego wszystkiego wynika wyraźnie, że add, ani remove nie zmieniają bezpośrednio wyrysowywanego komponentu na ekranie. Tak więc metoda remove() nie miałaby i tak większego znaczenia. Możesz to jeszcze sprawdzić usuwając ją. Może jeszcze wyjdzie z tego coś również nieoczekiwanego.

Jeszcze tylko pytanie po co ściągasz wszystko do stringów i potem je porównujesz? To raczej przestarzałe podejście. Łatwiej i taniej jest porównywać referencję źródeł zdarzenia bo porównanie sprowadza się do równości liczby 32-bitowej (co można załatwić prostym switchem lub wręcz tablicą), zamiast żmudnego porównywania łańcuchów znakowych.
Poza tym ciąg niezwiązanych ze sobą "ifów" to świetna okazja do późniejszej pomyłki.

0
  1. validateTree() nie zastępuje pary validate() i repaint(),
  2. remove() jest zbyteczne,
  3. na początku był tylko jeden przycisk "Następny panel", zatem nie musiał (i nie był) polem w klasie, potem w ramach mojej próby zrozumienia co się dzieje przyciski się mnożyły i zdawało mi się, że szybciej zmodyfikuję kod rozpoznając przyciski za pomocą getActionCommand() niż przerabiając je na pola w klasie.
0

Mała poprawka, remove() też jest konieczne.

0

Jezeli chcesz to rozwiazac w sposob, jaki podales w pierwszym poscie, po zamianie komponentow potrzebujesz wywolac validate() lub ew. validateTree(), zeby przekalkulowac nowy rozklad komponentow, a nastepnie repaint() zeby odrysowac nowy uklad.

Nie wiem jednak, czy to, co chcesz zrobic nie da sie rozwiazac przy pomocy bardziej czytelnego java.awt.CardLayout.

0

@ws, już odkryłem (i napisałem na forum) , że sekwencja remove => add => validate => repaint działa zawsze. Zagadkowe jest to, że jeżeli dodawany jest panel, który jeszcze nie był wyświetlany, to repaint nie jest konieczne.
Jak pisałem powyższe zdanie, to przyszła mi do głowy taka myśl: może obiekt klasy JPanel jakoś pamięta, że nie był jeszcze wyświetlany i w opisanej sytuacji repaint() jest wykonywana niejako automatycznie.

0
bo napisał(a)

@ws, już odkryłem (i napisałem na forum) , że sekwencja remove => add => validate => repaint działa zawsze.

W porzadku. Chodzilo mi tylko o uscislenie, jakie metody nalezaloby wywolac po zmianie organizacji komponentow, jak w Twoim przypadku.

bo napisał(a)

Zagadkowe jest to, że jeżeli dodawany jest panel, który jeszcze nie był wyświetlany, to repaint nie jest konieczne.
Jak pisałem powyższe zdanie, to przyszła mi do głowy taka myśl: może obiekt klasy JPanel jakoś pamięta, że nie był jeszcze wyświetlany i w opisanej sytuacji repaint() jest wykonywana niejako automatycznie.

Mysle, ze artykul http://java.sun.com/products/jfc/tsc/articles/painting/ w pelni wyjasnia takie zachowanie.
Pozdr.

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