Klasa odwołująca się do JLabel z innej klasy

0

Witam, nie wiem czy temat zbytnio nie namieszał, ale do rzeczy.
Napisałem aplikację operującą na plikach (w przybliżeniu kilkuset).
W ramach postępu nauki, dorzuciłem do projektu GUI (rozszerzając JFrame) a czynność operowania na plikach podpiąłem pod button.
Jako, że operacje na plikach blokowały menu, operacje na plikach przerzuciłem do nowego wątku.

Klasa z obliczeniami wyświetla w konsoli na którym aktualnie pracuje pliku (np. 1/250).
Jednak aplikacja ma nie działać w konsoli. I zastanawiam się już drugi dzień (szukając po forach) jak można by te wyświetlanie przerzucić np. do JLabel. Ale niestety nie mam bladego pojęcia jak można to zaimplementować.

Myślę nad stworzeniem nowego wątku, który by sprawdzał cały czas wartość zmiennej (odpowiedzialnej za aktualny plik), lecz jak to zrobić by taki wątek nie blokował menu ?

Mam wielką nadzieję, że ktokolwiek zrozumie o co mi chodziło. W razie czego będę starał się to opisać innymi słowami.
Pozdrawiam.

0

Nie rozumiem jaki jest problem. Nie możesz do wątku przetwarzającego przekazać referencji do kontrolera który będzie zmieniał to co wyświetla ci JLabel?

0

Do tego co robisz i do tego, co chcesz zrobić jest klasa SwingWorker. Pozwoli ci robić operacje w wątku pobocznym i przesyłać komunikaty do wątku interfejsu (EventDispatchingThread).
Jest jeszcze opcja taka, że tworząc obiekt, w którym jest wątek operujący na plikach przekażesz mu w konstruktorze referencję do obiektu JTextArea (zamiast JLabel). Obiekt ten (klasa) może być użyta jako wyjście z konsoli. A dokładnie ta klasa, ponieważ ma ona metody "thread safe" do wypisywania tekstu, więc ma szanse zadziałać. Ale nie testowałem tego sposobu (używam SwingWorker w takiej sytuacji).

0

no właśnie nie wiem jak mogę zrobić by JLabel "nasłuchiwał" zmian. Bo z tego co rozumiem, JFrame (całe moje menu) zmienia się wtedy gdy wykonam jakiegoś ActionListnera. A przecież sama zmiana stringa (wywołana kontrolerem) nie odświeży mi JFrame.
Bo Twoją radę rozumiem mniej więcej tak:

public void Przetwarzanie()
{
long licznik=1;
   for (String nazwa:BazaPlików)
   {
            Operacje na pliku;
            licznik++;
            Ramka.setTekst(licznik + " / " + BazaPlików.length);
    }
}


public class Ramka
{
     private static JLabel Wyświetl = new Jabel();
     private static Tekst_w_JLabel;

     public void setTekst(String tekst)
    {
        Tekst_w_JLabel = tekst;
     }     

    public Ramka()
    {
         Wyświetl.setText(Tekst_w_JLabel);
     }
   
}

To bardziej pseudokod, co nadal nie zmienia faktu, że nie wiem jak to odświeżyć ?

Edit: Nie widziałem drugiej odpowiedzi, w takim razie póki co dziękuję i zaczynam czytać o SwingWorker. A jakby ktoś miał jeszcze jakieś pomysły to chętnie posłucham - dopiero się uczę więc im więcej tym lepiej :)

chodnik napisał(a):

Do tego co robisz i do tego, co chcesz zrobić jest klasa SwingWorker. Pozwoli ci robić operacje w wątku pobocznym i przesyłać komunikaty do wątku interfejsu (EventDispatchingThread).

Poczytałem ile mogłem, ale nie specjalnie rozumiem (naturalnie będę jeszcze zgłębiał temat). Pozwoliłem sobie skopiować z pewnej strony jakiś schemat, jednak nawet nie wiem z jakiej strony się za to zabrać, mianowicie :

 
                   SwingWorker worker = new SwingWorker() {
	            @Override
		            protected void process(List chunks) {
		                // Aktualizujemy status zadania wykorzystując fakt, że metoda
		                // process() wykonywana jest w EDT.
		                Wyświetl.setText(Pobranie.getLicznik().toString());             // tutaj ustawiłem by JLabel Wyświetl zmieniał się na Licznik.
		            }
	            	
		    @Override
		    protected Object doInBackground() throws Exception {
		                try {
		                   Pobranie.Główne(1, "test.txt");             // tutaj mam główne instrukcje (bardzo czasochłonne).
              
		                } 
		                catch (FileNotFoundException e1) 
				        {
					    	błąd=true;
				        } 
				        catch (IOException e1) 
				        {
					    	błąd=true;
				        }

		                return null;
		            }

		            @Override
		            protected void done() {
		                // Aktualizujemy status zadania wykorzystując fakt, że metoda
		                // done() wykonywana jest w ETD.
		                Wyświetl.setText("Gotowe");
		                
		            }
		        }
		        worker.execute(); // Uruchamiamy SwingWorker'a, aby nie blokować EDT.

Wiem, że powinienem najpierw przeczytać wszystko co się da a dopiero później zacząć coś kopiować i próbować, jednak zawsze najlepiej wychodziła mi nauka na gotowych przykładach - które ktoś objaśnia.
Więc jakby ktoś był na tyle uprzejmy by wytłumaczyć mi co zrobić, by licznik :

Wyświetl.setText(Pobranie.getLicznik().toString());      

zmieniał się w czasie rzeczywistym.
Pozdrawiam

1

Najprościej to przekazujesz referencję tego JLabela do swojego wątku i ustawiasz tekst label.setText("foo"). Tylko, musisz to zrobić w "swingowym" wątku np. poprzez metodę SwingUtilities.invokeLater(Runnable doRun)

SwingUtilities.invokeLater(
  new Runnable() {
    public void run() {
      wyswietl.setText("foo");
    }
  }
);
0

Jak już zabierasz się za SwingWorker - po przerobieniu jednego pliku wywołujesz metodę publish, to powoduje "odświeżenie" tego co robisz w process, które to jest wywoływane w wątku "swingowym".

2

W metodzie doInBackground obiektu SwingWorker<RESULT,PROGRESS> zazwyczaj jest jakaś pętla. W niej co jedną iterację wywołujesz metodę publish(PROGRESS... wartościPośrednie) z jednym lub kilkoma argumentami, które podają kolejne stany postępu. Typem PROGRESS jest jakiś typ porządkowy - zazwyczaj obiektowy int, czyli Integer, a zakresem wartości [0,100], co dobrze obrazuje procentowy postęp zadania. U Ciebie zakresem może być [1,250].
Wywołanie publish z argumentem konkretnej wartości typu PROGRESS powoduje to, że niedługo potem* pojawia się w Swingu zdarzenie, które skutkuje wywołaniem z wątku EDT (czyli tym na którym są obsługiwane zdarzenia GUI kontrolek Swinga) metody process tego obiektu SwingWorkera, u którego nastąpiło wywołanie publish. Dzięki temu, że metodą tą porusza wątek EDT można bezpośrednio z niego ustawiać wszystkie kontrolki - na przykład różne JLabele za pomocą ich setText, które obrazują stan licznika/postępu, i to bez pośrednictwa metody invokeLater.
Normalnie w publish jest tylko jedna bieżąca wartość pośrednia, ale może się zdarzyć, że wywołanie w wątku EDT tak się opóźni, że w tym czasie wywołają się dwie lub więcej metody publish. Dlatego w process dostajesz listę kolejnych wartości, a nie samą ostatnią wartość (można brać z listy tylko ostatnią z wartości dla zobrazowania postępu).
Ostatecznie, kiedy zakończy się pętla w doInBackground, a zwykle i cała ta metoda, wtedy w podobnym trybie wywoływana jest z wątku EDT metoda done, która służy zazwyczaj do posprzątania i ewentualnie ostatecznego wyświetlenia wyników na jakiejś kontrolce.

Formalnie wartością wywołania konstruktora SwingWorkera jest coś takiego jak Future<RESULT> (SwingWorker implementuje taki interfejs). Sam typ Future to taka śmieszna wartość, która "opakowuje" dane dostępne w przyszłości. Wywołanie jej metody get zanim dane będą dostępne spowoduje oczekiwanie aż wykona się cały doInBackground, który te dane uzyska - spowoduje to oczywiście przyblokowanie wykonywania wątku, więc nie wolno tego wywoływać z wątku EDT (na przykład z obsługi jakiegoś listenera). Dopiero kiedy zakończy się praca, czyli gdy metoda SwingWorker.isDone będzie zwracać true można wywołać get bez obawy o przestoje - dane będące wynikiem zostaną udostępnione od razu (wtedy będzie już ta "przyszłość").

Można też pominąć SwingWorkera i robić wszystko na osobnym wątku - takim jaki stworzyłeś. Ale wtedy musisz sobie sam zorganizować aktualizację licznika i dostęp do niego z Twoich kontrolek. Robi się to zwykle przez zrobienie licznika polem obiektu z modyfikatorem volatile (może być prywatne z dostępem przez gettera), a z pętli w której się kręci musisz sam wywoływać sobie za pomocą invokeLater jakieś polecenie setLabel z nową wartością a następnie kontrolki te aktualizować przez swingową metodę validate i na koniec repaint aby kontrolki przerysowały się z nową aktualną zawartością.

Właściwie SwingWorker w tak prostych przypadkach nie daje żadnej istotnej przewagi poza standaryzacją wykonywania takich zadań. Jednak ponieważ kolejne zadania mogą być coraz bardziej skomplikowane, to warto nauczyć się jak używać tej klasy.

Co do zasady różnica między wyświetlaniem w konsoli, a pracą z oknami polega na tym, że w konsoli to ty wyrzucasz dane na ekran/konsolę, co programujesz w jakiejś pętli. W przypadku okien Twoje programowanie sprowadza się do tego aby swoim kontrolkom dać przepis na to jak te dane na bieżąco uzyskiwać. Kontrolki są kontrolowane i odrysowywane przez system, więc aby mogły wyświetlić np. jakiś tekst, to muszą wiedzieć jak się do niego dobrać. I tu do gry wchodzi Twój kod, który wie jak te dane uzyskać. Krótko mówiąc przy tradycyjnym programowaniu ty dane pchasz do konsoli, natomiast przy programowaniu zdarzeniowym kontrolki sobie te dane same ciągną na bieżąco.
Natomiast jeżeli tak jak w tym wypadku ty musisz te dane aktywnie zmieniać (czyli tradycyjnie), to oprócz aktualizowania tych danych dla kontrolek musisz te kontrolki dodatkowo informować, że coś się w tych danych zmieniło. Bez tej informacji kontrolki mogą "uważać", że żadne dane się nie zmieniły i wciąż będą wyświetlać to co wcześniej.

Tak mniej więcej wygląda Twój problem i takie jest rozwiązanie.

    • nad tym odstępem czasu nie masz żadnej kontroli poza zapewnieniem, że będzie to tak szybko jak to możliwe.

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