GUI Benachrichtung (MVC?)

bOOgey

Mitglied
Hallo liebe Java-Communitiy,

ich bin ein faszinierter junger Java-Programmierer der sich grundsätzlich ein bisschen mit der GUI-Programmierung ziemlich schwer tut. Mein generelles Problem ist immer die richtige Benachrichtigung der GUI-Elemente.

Ich versuche in allen meinen Applikationen immer einen MVC bzw. ein MVP zu implementieren.
Auf der einen Seite habe ich die GUI mit ihren vielen Elementen. Dazu schreibe ich zu jedem "wichtigen" Element Getter und Setter. Dann kommt die Logikschicht mit Datenbank-Verbindung oder File-Objekten. Je nachdem was gerade gebraucht wird.
Als Bindeglied habe ich zwischen den beiden Schichten immer ein Controller, der beide Klassen kennt und zwischen ihnen kommuniziert. (Grundsätzlich richtig?)
Nun habe ich aber das Problem, dass sich beispielsweise eine Liste ändert und ich irgendwie versuche die GUI darüber zu benachrichtigen. Leider mit mehr oder weniger schlechter Erfolgschance.
Java:
 repaint()
oder
Java:
 validate()
bringen oft nicht den gewünschten Effekt.

Aktuell versuche ich ein Art "Open Ressource", welcher in Eclipse (Ctrl + Shift + R) beispielsweise vorzufinden ist für ein anderes kleines Projekt zu implementieren. Leider stoße ich an gewisse Grenzen, was die Performance angeht.
Zu Beginn ermittle ich alle Dateien aus einem bestimmten Pfad, welchen ich per Text-Dokument übergebe.
Java:
 public class SearchDirectory implements Runnable {

    private String searchDir;
    private LinkedList<Modul> files;
    private Controller controller;

    public SearchDirectory(Controller controller) {
        this.files = new LinkedList<Modul>();
        this.controller = controller;
    }
    
    public SearchDirectory() {
        this.files = new LinkedList<Modul>();
    }

    public void search() {
        File startSearch = new File(searchDir);

        if(startSearch.exists()) {
            this.processSearch(startSearch);
        } else {
            System.out.println("Das Verzeichnis " + searchDir + " existiert nicht!");
        }
    }

    private void processSearch(File searchDir) {
        File[] childDirs = searchDir.listFiles();
        for(File child : childDirs) {
            if(child.isDirectory()) {
                processSearch(child);
            } else {
                this.files.add(new Modul(child));
            }
        }
    }

    public void readFile() throws IOException {
        InputStreamReader is = null;
        try {
            is = new InputStreamReader(ClassLoader.getSystemResourceAsStream("de/searchgui/io/Path.txt"));
            StringBuilder sb = new StringBuilder();
            int letter;
            while((letter = is.read()) != -1) {
                sb.append((char) letter);
            }
            this.searchDir = sb.toString().trim();
        } catch(FileNotFoundException fnfe) {
            System.err.println(fnfe);
        } finally {
            if(is != null) {
                is.close();
            }
        }
    }

    public LinkedList<Modul> getFiles() {
        return this.files;
    }

    public void run() {
        try {
            readFile();
            search();
            controller.createNewList();
        }  catch (IOException ex) {
            System.err.println(ex);
        }
    }
Wenn dieser alle Dateien gefunden hat, wird der Controller und somit die GUI informiert

Java:
public class Controller {

    private SearchFrame frame;
    private DefaultListModel model;
    private SearchDirectory searcher;
    private LinkedList<Modul> removeList;

    public Controller() {
        try {
		UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
	} catch (Exception e) {
		System.err.println(e.toString());
        }
        this.frame = new SearchFrame("Modulsearcher");
        this.model = new DefaultListModel();
        this.searcher = new SearchDirectory(this);
        this.removeList = new LinkedList<Modul>();
    }

    public void programStart() {
        SwingUtilities.invokeLater(searcher);
        frame.setListModel(model);
        frame.initFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setUndecorated(true);
        frame.setLocation(MouseInfo.getPointerInfo().getLocation());
        frame.setSize(new Dimension(350, 300));
        frame.setVisible(true);

        frame.addBtnOKListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                executeGIT();
            }
        });

        frame.addBtnCancelListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                exitWindow();
            }
        });

        frame.addModulKeyListener(new KeyAdapter() {

            @Override
            public void keyReleased(KeyEvent e) {
                actionOnKey(e);
            }
        });
    }

    public void actionOnKey(KeyEvent e) {
        switch(e.getKeyCode()) {
            case KeyEvent.VK_ENTER:
                System.out.println(frame.getModul());
                frame.dispose();
                break;
            case KeyEvent.VK_ESCAPE:
                System.out.println("");
                frame.dispose();
                break;
            case KeyEvent.VK_F1:
                JOptionPane.showMessageDialog(frame, "You pressed F1");
                break;
            default:
                containFilelist();
                break;
        }
    }
    
    public void createNewList() {
        frame.enableFrame(false);
        model.clear();
        List<Modul> modulList = searcher.getFiles();
        for(int i = 0; i < modulList.size(); i++) {
            model.add(i, modulList.get(i));
        }
        frame.enableFrame(true);
    }
    
    public void containFilelist() {
        frame.enableFrame(false);
        model.clear();
        List<Modul> modulList = searcher.getFiles();
        for(int i = 0; i < modulList.size(); i++) {
            Modul tmpModul = modulList.get(i);
            if(tmpModul.getFilename().startsWith(frame.getValue())) {
                model.add(model.getSize(), tmpModul);
            }
        }
        frame.enableFrame(true);
    }

    public void exitWindow() {
        frame.dispose();
    }

    public void executeGIT() {
        try{
            new ProcessBuilder("gitk " + frame.getModul()).start();
        } catch (IOException ioe) {
            System.err.println(ioe);
        }
    }
}
Grundsätzlich ist der Abschnitt mit dem KeyListener besonders langsam. Gibt es irgendeine andere Variante, wie man an diese Problematik herangehen kann? Letztendlich müssen sehr viele Einträge verwaltet werden. Also wir sprechen hier von knapp 28000 Elementen im Model.

Wäre für eure Tipps dankbar.

Zusätzlich würde ich mir wünschen, wenn mir einer diesen MVC, mit Schwerpunkt auf die GUI-Benachrichtung erklären könnte. Ich habe zwar diverse Tutorial und Erklärungen mit angeschaut, nur leider werfen diese immer wieder Fragen auf. Besonders was eben die Benachrichtung von etwas komplexeren Komponenten angeht. Z. B. JTable, JTree, JList.
Bisher war mein letzter Ausweg, die Komponente zu entfernen und dann eine neue versuchen an der richtigen Stelle hinzuzufügen. (Schlechte Variante die in mir Unbehagen auslöst)

Wäre für genaue Erklärungen sehr dankbar



So Long

bOOgey

P.S. Bitte nicht wundern über einige Code-Leichen und sinnfreien Aufrufe. Bin wie gesagt noch nicht fertig und zufrieden
 
S

SlaterB

Gast
> der Abschnitt mit dem KeyListener besonders langsam

der KeyListener macht nicht viel, er wertet Tasten aus, eine davon reagiert, indem sie 10.000de Elemente hin und her kopiert,
diese Arbeit ist aufwendig, mit dem KeyListener oder MVC oder irgendwas ähnlichem hat das nicht so viel zu tun

warum diese Aktion langsam ist kann man untersuchen, z.B. die Unterscheidung nach value herausnehmen und immer alle anzeigen, macht das einen Unterschied?

alle Elemente einzeln per add() ins Model einzufügen scheint mir aber der erste Verdächtige für Langsamkeit zu sein,
bei jedem Aufruf informiert das DefaultListModel seine Listener über diese Änderung,

baue ein eigenes Model, kopiere vielleicht von
Java > Open Source Codes > javax > swing > DefaultListModel _ Java API By Example, From Geeks To Geeks.
und setze die Daten mit einem Aufruf + nur einmal fireContentsChanged() oder fireIntervalRemoved() + fireIntervalAdded(),

> was eben die Benachrichtung von etwas komplexeren Komponenten angeht. Z. B. JTable, JTree, JList.
jede Komponente hat ein Model, welches ihre Listener, JTable & Co., bei Änderungen informiert

am ehesten noch kann man das ganze Model einer Komponente austauschen, dann wird auch im neuen geschaut wie es aussieht,
neue GUI-Elemente oder remove/add aus Panel und validate/repaint sollten bei reinen Datenänderungen nicht nötig sein
(abgesehen vielleicht von einem JLabel mit breiteten Text),

die GUI aktualisieren muss man nur wenn man auch neue GUI-Elemente hinzufügt, was praktisch nie mitten im Programm der Fall sein sollte
 

bOOgey

Mitglied
Das ganze Programm soll eben so funktionieren wie das "Open Ressource" wie bei Eclipse.
Die GUI dort besteht aus 2 Elementen. Einmal ein Textfeld und einmal eine Liste. Zu Beginn werden alle Dateien in die Liste geladen -> man sieht alle Dateien.
Dann, wenn man nun im Textfeld was eingibt soll sich die untere Liste einschränken, bzw. beim löschen wieder neu aufbauen. Ich hatte ja schon herausgefunden, dass das Iterieren von 28000 Elementen nicht das langsame ist sondern eben die Aktualisierung der GUI. Deswegen auch dieser Thread, wie man dies nun am besten gestaltet.
Deine Erklärung diesbezüglich fand ich schon sehr Hilfreich. Also prinzipiell liegt das Problem darin, dass das Modell ständig beim Aufruf von add/remove die GUI aktualisiert und das eben ewig dauert. Das bringt mich schon weiter.
Dennoch habe ich noch eine Frage zum Aktualisieren.

Man stelle sich vor, ich habe ein JTree und dort soll an einem bestimmten Knoten über eine Dialog-Box etwas eingefügt werden. Bei der Box werden also die Daten, welche im Modell enthalten ist geändert. Wie schaffe ich es, dass sich der JTree aktualisiert, ohne eben das Modell auszutauschen. Muss ich aus dem Dialog beim ändern eine "fire"-Methode aufrufen oder gibt es eine Möglichkeit dies automatisiert abzulaufen? (Flag-setzen wie beim Observer?)
 
S

SlaterB

Gast
JTree ist wirklich die komplizierteste Struktur, habe ich persönlich so wie angesprochen noch gar nicht verwendet,
wahrscheinlich muss man dann
public void valueForPathChanged(TreePath path,
Object newValue)

Messaged when the user has altered the value for the item identified by path to newValue. If newValue signifies a truly new value the model should post a treeNodesChanged event.
des TreeModels aufrufen, notfalls für alle aktuell ausgeklappten Elemente
 
A

Alex1234567890

Gast
Wahrscheinlich ist diese Zeile ein Problem:

SwingUtilities.invokeLater(searcher);

Die Methode invokeLater soll eigentlich nur "kleine" Threads bekommen die z.B. einen Text in ein Textfeld einfügt.
Am besten searcher als normalen Thread starten und nur die Zugriffe auf die GUI mit invokeLater.

Gruß

Alex
 
Ähnliche Java Themen

Ähnliche Java Themen

Neue Themen


Oben