Swing i komunikat dla uzytkownika

0

Mam okno w którym po naciśnięciu przycisku wykonuje się akcja która trwa kilka sekund.
Przez ten czas aplikacja wisi (co rozumiem i akceptuje),
ale chciałbym poinformować użytkownika aby chwilkę poczekał.

public class Frame extends JFrame {

    public Frame() {
        JButton button = new JButton("Action");
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                action();
            }
        });
        add(button);
        pack();
    }

    private void action() {
        //Jak tutaj wyswietlic Dialog/Popup z informacja aby uzytkownik czekal
        try {
            Thread.sleep(5000);
        } catch (InterruptedException ex) {

        }
        //a tutaj zamknąc ten Dialog/Popup
    }
}

Domyślam się, że muszę utworzyć ten Dialog/Popup wcześniej jako nowy wątek,
ale jak go poinformować, aby się pojawił i zniknął?

0

Po znalezieniu tego:

http://4programmers.net/Forum/viewtopic.php?id=132373

(Dzięki Olamagato)

Zrobiłem tak:

private void action() {
    final JDialog dialog = new JDialog(this, true);
    dialog.setUndecorated(true);
    dialog.add(new JLabel("Komunikat"));
    dialog.pack();

    new SwingWorker() {
        @Override
        protected Object doInBackground() throws Exception {
            while(!dialog.isVisible()) Thread.sleep(1);

            //jak zamiast Thread.sleep(1) dam wait(1)
            //to za drugim uzyciem action(), wait(1) sie zatrzymuje
            //moglby mi ktos to wytlumaczyc?

            try {
                Thread.sleep(5000);
            } catch (InterruptedException ex) {}

            dialog.dispose();
            return null;
        }
    }.execute();

    dialog.setVisible(true);
}
0

Nigdy nie używaj wait() w SwingWorkerze jeżeli nie potrafisz powtórzyć kodu źródłowego tej klasy wyrwany ze snu o trzeciej w nocy. :)
"Metoda wait() pozwala na zawieszenie wykonania zadania w oczekiwaniu na zmianę tych warunków wykonania, które pozostają poza jego kontrolą" (Bruce Eckel).

SwingWorker posługuje się synchronizacją, zatrzymywaniem i uruchamianiem swojego wątku, więc używając wait() tylko zaburzasz działanie tego kodu. Dlatego robiąc to musisz doskonale wiedzieć co robisz. Najwyraźniej tego nie wiesz i jedyne co może Ci pomóc w odpowiedzi na pytanie z komentarza, to analiza kodu źródłowego SwingWorkera i wszystkich powiązanych z nim klas (co może być trudne jeżeli natkniesz się na metody z pakietów Suna lub metody native).

Co do samego problemu. Zacząłeś prawidłowo, ale nie dokończyłeś i trochę namieszałeś.
Po pierwsze nie wolno Ci odpalać żadnej metody Swinga w metodzie doInBackground() - przede wszystkim sprawdzenia stanu okna. To jest inny wątek niż Swing. Nie ma więc też sensu robienie pętli opóźniających w tej metodzie - to jest Twoje miejsce wykonania pracy, którą zleciłeś, a nie oczekiwania na cokolwiek. SwingWorker dowie się o skończeniu tej pracy gdy ta procedura się zakończy.

Powinieneś zrobić tak:
W metodzie action blokujesz przycisk, tworzysz dialog (może być z paskiem postępu oraz przyciskiem anulowania) i odpalasz go. Następnie odpalasz przez execute klasę wywiedzioną ze SwingWorkera, w której metodzie doInBackground() robisz to co miało naprawdę robić Twoje wywołanie akcji. Jeżeli jest tam jakaś pętla, to wywołujesz w niej publish(), aby poinformować o postępie tego zadania. W metodzie process() (którą kieruje Swing) otrzymujesz wysłane przez publish() informacje o postępie dzięki którym możesz aktualizować na bieżąco pasek postępu czy jakiś zegar. Dialog informujący o konieczności czekania zamykasz w metodzie done() Twojego SwingWorkera (stąd konieczność final). Kiedy jest ona odpalana, to już ani razu nie odpali się process() bo praca w tle już się zakończyła, a on otrzymuje przecież dane z publish() wywoływanej w doInBackground().
NIE UŻYWASZ SLEEP, ANI TYM BARDZIEJ WAIT. Używając SwingWorkera możesz o nich zapomnieć bo są to procedury niższego poziomu w stosunku do niego. Dodatkowo na samym początku procedury action() powinieneś wyłączyć komponent, który wyzwolił to zadanie, a w metodzie done() SwingWorkera ponownie go włączyć. Inaczej po wielokrotnym naciśnięciu przycisku miałbyś całe stado okienek dialogowych z różnymi stanami postępu. Na procesorach z małą ilością rdzeni (lub jednordzeniowych) ich prędkości wyrównałyby się i zakończyłyby się w bardzo podobnym czasie (a nie miarowo - po kolei zgodnie z momentami odpalania przycisku wyzwalającego). To taka specyfika pracy wielozadaniowej i symulowania wątków.

Całe działanie po naciśnięciu przycisku działa wtedy tak:

  1. Odpala się przycisk. Swing dostaje komunikat, szuka właściwego listenera i odpala jego metodę action().
  2. W action() blokujesz możliwość przyciśnięcia tego przycisku.
  3. Wraca z action() jeżeli chwilę przedtem wcześniejsze wywołanie action() już się uruchomiło i zablokowało przycisk - można to sprawdzić na dwa sposoby: metodą !isDone() jeżeli dziecko SwingWorkera zostało przypisane jakiejś zmiennej (w tym celu zresztą przypisanie), albo sprawdzić stan blokady przycisku wywołującego (jeżeli zablokowany, to wcześniejsze action() zdążyło się już uruchomić) - wtedy klasa wywiedziona ze SwingWorkera może być wywołana jako anonimowa i nigdzie nie przypisywana. [Uwaga - formalnie odśmiecacz powinien taką klasę usunąć, ale nie zrobi tego nigdy z klasami pracujących wątków. Zrobi to dopiero po zakończeniu metody Done() SwingWorkera.]
  4. Tworzy się okno dialogu, dodaje go jako dziecko do systemu okien Swinga i wywołuje procedurę wyświetlenia (zlecane jest to Swingowi). Nad tym oknem pełną kontrolę ma Swing, więc gdyby action() teraz zawisło (kręci nim przecież wątek Swing), to również i to nowe okno.
  5. Odpala się metoda execute() klasy wywiedzionej ze SwingWorkera. Tworzony zostaje nowy wątek odpalający po rożnych działaniach metodę doInBackground(). Sterowanie jednak natychmiast wraca z execute () do action().
  6. Kończy się metoda action(), której całe wykonanie trwa nanosekundy.
    W tym czasie kręci się już Twoja metoda doInBackground() w swoim wątku (a tylko nad nią masz pełną władzę). Swing może już sterować okienkami (w tym utworzonym dialogiem), metodą process() i później done() tego konkretnego obiektu SwingWorkera.

Po powrocie z action() okresowo będzie uruchamiana przez Swing procedura process() Swingworkera. W "magiczny sposób" (przez mechanizmy synchronizacji SwingWorkera) będzie ona dostawała dane z wątku doInBackground() za pośrednictwem metody publish(). Będzie się tak działo aż do zakończenia doInBackground(), co da sygnał SwingWorkerowi do wrzucenia na kolejkę działań Swinga wywołania przez Swing metody done(). W done() zostanie odblokowany przycisk wyzwalający działanie, a po jej zakończeniu odśmiecacz sprzątnie nieaktywny już obiekt wywiedziony ze SwingWorkera. Od tego momentu Cała Twoja aplikacja wraca co stanu początkowego.
Cykl może się rozpocząć od nowa.

0

Dziękuje za wyjaśnienie jak to wszystko działa.
Chciałbym dopytać o kwestie blokowania przycisku.
Czy mogłaby być ona zrealizowana poprzez modalny dialog?

Czyli w moim przypadku zostawić dialog.setVisible(true) na końcu metody action(),
a w SwingWorker w metodzie done() po prostu go zwolnic: dialog.dispose()

0

Tak. To powinno zadziałać prawidłowo.

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