2011-01-17 7 views
11

Jakiś czas temu zapytałem this question. Wszystkie rozwiązania są rozwiązaniem.Czy coś jest nie tak z implementacją MVC Swing dla JList?

Teraz to nie może być. Czuję, że coś tu jest nie tak, ale nie mogę powiedzieć, czy to jest model MVC Swinga, który jest koncepcyjnie zły, czy też jest to moje myślenie, które jest koncepcyjnie błędne.

Oto problem ponownie. Używam JList do implementacji listy miniatur dla stron dokumentu. Jeśli użytkownik wybierze kolejną miniaturę z listy, ta strona zostanie załadowana. W tym celu dodałem ListSelectionListener do JList, który po zmianie wyboru ładuje tę stronę. Ale użytkownik może również zmienić stronę za pomocą innej kontrolki. Naturalnie chcę, aby znalazło to odzwierciedlenie na liście miniatur poprzez wybór tej strony tutaj. Więc setSelectedIndex() zaktualizować JList. Niestety ma to niepożądany efekt podniesienia ListSelectionEvent, co powoduje odświeżenie strony przez słuchacza.

Co jest nie tak? Właśnie zmieniłem model z innego miejsca, więc naturalnie chcę, aby widok się zaktualizował, ale nie chcę, aby powodował zdarzenia. Czy Swing nie wdraża MVC, prawda? Czy może brakuje mi tutaj punktu?

+0

Dzięki wszystkim za wielkie odpowiedzi!Przyjąłem odpowiedź @ britishmutt, ponieważ jest ona najbardziej szczegółowa i wnikliwa i zawiera najczystsze rozwiązanie. Problem polega na tym, że komponent ładujący stronę powinien zobaczyć, że zażądano załadowania tej samej strony i nie powinien tego robić. Linki były bardzo przydatne. Nadal uważam, że model MVC Swinga jest wadliwy. Powinny one pójść tradycyjnie. Ich model wydaje się być większym kłopotem, niż jest wart. –

+0

Napotkałem ten sam problem z aktualizacjami programu nasłuchującego kilka razy. Wyobraź sobie, że masz N komponentów, które muszą się wzajemnie aktualizować ... Nawet jeśli sprawdzisz prawdziwą zmianę wyświetlania, aby zdecydować, czy chcesz wystrzelić jakieś wydarzenie, będą zdarzenia N-1 wystrzelone z innych komponentów N-1 po oni są na bieżąco. – Timmos

Odpowiedz

9

Jest to problem, z którym musi zmierzyć się wielu programistów Swing: wiele elementów sterujących modyfikujących te same dane, a aktualizacja jest odzwierciedlana w każdym sterowaniu. W pewnym momencie coś musi mieć ostateczne weto dotyczące tego, jakie aktualizacje zostaną zastosowane do modelu: cokolwiek to coś musi być w stanie obsłużyć wiele (potencjalnie redundantnych lub nawet sprzecznych) aktualizacji i zdecydować, co z nimi zrobić. Może się to zdarzyć w warstwie modelu, ale najlepiej powinien to być kontroler, który to robi - w końcu ten element najprawdopodobniej znajduje się tam, gdzie znajduje się logika biznesowa.

Problem z Swingiem polega na tym, że część kontrolera MVC jest często dzielona pomiędzy komponent widoku a model, więc może być trudno scentralizować tę logikę. Do pewnego stopnia interfejs Action koryguje to dla zdarzeń actionPerformed(), umieszczając logikę w jednym miejscu i umożliwiając jej współdzielenie przez różne komponenty, ale to nie pomaga dla innych typów zdarzeń lub gdy istnieje wiele różnych klas zdarzenia, które musi być skoordynowane.

Odpowiedź jest następująca po wzorcu, który jest zasygnalizowany w Swing, ale nie jest wyraźny: wykonuj tylko żądaną aktualizację, jeśli stan się zmieni, w przeciwnym razie nic nie zrobisz. Przykład ten znajduje się w samym JList: jeśli spróbujesz ustawić wybrany indeks JList do tego samego indeksu, który został już wybrany nic się nie stanie. Żadne zdarzenia nie zostaną uruchomione, żadne aktualizacje się nie pojawią: żądanie aktualizacji jest skutecznie ignorowane. To coś dobrego. Oznacza to, że możesz na przykład mieć słuchacza na swoim JList, który zareaguje na nowo wybrany element, a następnie poprosi tego samego JList o ponowne wybieranie tego samego przedmiotu i nie utknie w patologicznie rekurencyjnej pętli. Jeśli wszystkie kontrolery modelu w aplikacji to robią, to nie ma problemu z wielokrotnymi, powtarzającymi się zdarzeniami wystrzeliwanymi w każdym miejscu - każdy komponent aktualizuje się sam (a następnie uruchamia zdarzenia), jeśli to konieczne, i jeśli aktualizuje się następnie może odpalić wszystkie potrzebne zdarzenia aktualizacji, ale tylko te komponenty, które nie otrzymały jeszcze wiadomości, zrobią wszystko.

+0

Doceniam szczegółową odpowiedź; daje wiele wglądu. Obawiałem się, że tylko ja doświadczam tego problemu i to mnie dręczyło, ponieważ pojawiło się kilka razy. To, co sugerujesz w ostatnim akapicie, dotyczy tego, co działo się na początku. Ale mimo że nie weszła w nieskończoną pętlę, wciąż ładowała stronę dwukrotnie. Nie podobają mi się te wszystkie wydarzenia latające w różnych miejscach. Przynajmniej cieszę się, że to ogólny problem i teraz czuję się mniej głupi. –

0

Zwykle sposób, w jaki działa słuchacz, będzie "gaśnie" za każdym razem, gdy wystąpi zdarzenie, na które czeka. Gdybym miał spekulować, to nieporozumienie z twojej strony.

3

Swing nie jest dokładnie MVC, ale ma swoje korzenie w MVC (różnica polega na tym, że widok i kontroler w Swingu są ściślej ze sobą spokrewnione niż w innych MVC, patrz Swing architecture po więcej szczegółów).

Ale to nie może wydawać się problemem, przed którym stoisz. Wygląda na to, że musisz zweryfikować w detektorach zdarzeń, typ zdarzenia i zdecydować, czy go zignorować, czy nie: Jeśli zdarzenie zostało utworzone na liście, zmień je. Jeśli został wywołany przez inną kontrolę, nie rób tego.

+0

Nie sądzę, żebym mógł sprawdzić skąd się wywodzi. Zdarzenie jest tworzone przez bibliotekę JList (lub przez jej dokładniejszy model), ale to samo dzieje się, gdy użytkownik kliknie element. Wydarzenie nie zawiera żadnych informacji o niczym wcześniej. –

+2

To nie jest to, skąd się bierze, że to, czy zdarzenie ma spowodować zmianę stanu modelu, a co za tym idzie, wyświetlenia. Niezależnie od tego, która część kodu jest ostatecznie odpowiedzialna za odświeżenie wyświetlania dokumentu, musi wiedzieć, który dokument jest aktualnie wyświetlany, a jeśli pojawi się monit o ponowne wyświetlenie tego samego dokumentu, powinien on zignorować zdarzenie. –

5

Jest to oczekiwane zachowanie.

Od Model-View-Controller [Wikipedia]:

W systemach zdarzeniami, model powiadamia obserwatorów (zwykle wyświetleń) gdy informacja zmienia się tak, że mogą one reagować.

Więc, kiedy zadzwonić setSelectedIndex na JList, aktualizujesz swój model, który następnie powiadamia każdy ListSelectionListener. Nie byłoby MVC, gdybyś mógł "po cichu" zaktualizować model, nie dając nikomu znać.

+0

Świetna oferta! Masz absolutną rację. Ale teraz, gdy o tym myślę, JList jest View, więc musi zostać zaktualizowany. Ale ListSelectionListener nie jest widokiem. To kontroler. Więc myślę, że to jest problem. Jak powiedział @OscarRyz, kontroler i widok są ze sobą ściśle powiązane. W rzeczywistości kontroler jest sprzężony z modelem. Dziwne! Ponieważ kontroler powinien słuchać działań użytkownika (takich jak kliknięcie innego elementu), a nie zmian w modelu. Więc myślę, że to jest problem. Zamiast addListSelectionListener(), powinno być coś takiego jak addItemClickedListener(). –

2

Co powiedział @dogbane.

Ale aby rozwiązać problem, należy dodać jakiś test stanu podczas słuchacza, aby sprawdzić, czy zdarzenie to należy zignorować. ListSelectionEvent ma metodę getValueAdjusting(), ale w większości jest to wewnętrzna. To, co musisz zrobić, to sam to zasymulować.

Na przykład, po zaktualizowaniu listy z zewnętrznego wyboru, otrzymasz kod taki jak ...

try { 
    setSelectionAdjusting(true); 
    /* ... your old update code ... */ 
} finally { 
    setSelectionAdjusting(false); 
} 

iw kodzie aktualizacji dla określania zakresu i dostępu kwestii ListSelectionListenerEvent

public void valueChanged(ListSelectionEvent e) { 
    if (!isSelectionAdjusting()) { 
     /* ... do what you did before ...*/ 
    } 
} 

są pozostawione jako ćwiczenie dla czytelnika. Będziesz musiał napisać setSelectionAdjusting i ewentualnie ustawić go również na innych obiektach.

+0

Brzmi jak kolejny hack do mnie. Nadal mam wrażenie, że jest tu coś konceptualnie nie tak. –

2

Nadal czuję, że jest tu coś konceptualnie nie tak.

mi współczuć, ale może się to przyczynić do uznania, że ​​nie ma prostego JList obserwowania ListSelectionModel. Zamiast tego masz JList i kilka innych kontrolek obserwujących hybrydowy model selekcji. W wersji @Taisin's example hybrydą jest CustomSelectionModel, która rozciąga się na DefaultListSelectionModel i umożliwia ciche zmiany. Gdy jest kompatybilny, można również udostępnić model, zgodnie z tym, co zostało opisane w tym artykule: question & answer i SharedModelDemo z samouczka.

Dla porównania ten thread powołuje się na artykuł Java SE Application Design With MVC: Issues With Application Design, który bardziej szczegółowo rozwiązuje problem.

+0

Dzięki za empatię! Tak, myślę, że to wydaje się najczystsze rozwiązanie. Ale jeśli myślisz o tym, to tak jakbyś rozwiązał ten konceptualny błąd w modelu Swing. W artykule, o którym wspomniałeś, jest to krok 3: 'Model jest aktualizowany. Powiadamia kontrolera o zmianie jego właściwości. "To nie jest MVC. Model nigdy nie powiadamia kontrolera. Powiadomi widok. Zastępując model, aby nie wyświetlać aktualizacji, skutecznie naprawiasz błąd występujący w kroku 3. –

+0

Masz rację, to nie jest czysty MVC; jest to _odłączna architektura modelu, cytowana przez @Ocarcaryz: http://java.sun.com/products/jfc/tsc/articles/architecture/#separable – trashgod

+0

I jeszcze jaśniej tutaj myślę: http://www.oracle.com /technetwork/articles/javase/index-142890.html#3 Nie wiem, dlaczego wybrali ten projekt. Powodem, dla którego mówią: "Korzystanie z tego zmodyfikowanego MVC pomaga bardziej całkowicie oddzielić model od widoku." Ale dlaczego jest tak dobrze? Jaki problem rozwiązuje? W tej chwili wydaje mi się to bardziej kłopotliwe. –

2

zawsze robię tak:

public class MyDialog extends JDialog { 
     private boolean silentGUIChange = false; 

    public void updateGUI { 
     try { 
      silenGUIChange = true; 

      // DO GUI-Updates here: 
      textField.setText("..."); 
      checkBox.setSelected (...); 

     } 
     finally { 
      silentGUIChange = false; 
     } 
    } 

    private void addListeners() { 
     checkBox.addChangeListener (new ChangeListener() { 
      public void stateChanged (ChangeEvent e) { 
       if (silentGUIChange) 
       return; 

       // update MODEL 
       model.setValue(checkBox.isSelected()); 
      } 
     }); 
    } 

} 
Powiązane problemy