GUI-Aktualisierungs-Probleme

jf

Bekanntes Mitglied
Hallo, ich möchte an dieser Stelle nochmal eine Ergänzung zu meiner alten Diskussion starten, da diese aufgrund der Länge eingeschlafen ist.

Es geht dabei darum, dass die Fortschrittsanzeige nicht durchgehen und kontinuierlich aktualisiert wird, sobald parallel sehr viele Elemente in eine JList geschrieben werden. Mich interessieren an dieser Stelle die Hintergründe dafür und ob es evtl. Techniken gibt, welche meiner Herangehensweise überlegen sind.

Ein weiteres Problem ist, dass das ganze Haupt-Fenster während des Ladevorgangs nicht aktualsiert wird, wenn ich zuvor einen Dialog öffne. Sicherlich ist mir hier ein Konzept-Fehler unterlaufen - ich würde gerne erfahren, wie man das richtig löst.

Ich habe meine Anwendung mal zu einer Demo abgespeckt, welche diese beiden Probleme aufzeigt:
Java:
package progress_bar;


import javax.swing.*;

import common.ui.files.FileDrop;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.*;


@SuppressWarnings("serial")
public class Demo extends JFrame {
    
	// singleton variable
	private static Demo m_instance;
    
	private JTabbedPane  m_TabsPane    = null;
    private JProgressBar m_progress    = null;
    private KeyListener  m_keyListener = null;
    
    private static final String APP_TITLE = "Demo";
    private final int DEF_WIDTH           = 800;
    private final int DEF_HEIGHT          = 600;


    // constructor
    public Demo() {
    	
        //-----------------------------------------------------------------------------
        // Creating the tabbed pane
        m_TabsPane = new JTabbedPane();
        
        // Creating the progress bar
        m_progress = new JProgressBar();
        m_progress.setVisible(false);
        
		// Adding the TabRowJacket
        this.getContentPane().setLayout(new BorderLayout());
		this.getContentPane().add(m_TabsPane, BorderLayout.CENTER);
		this.getContentPane().add(m_progress, BorderLayout.SOUTH);
        //-----------------------------------------------------------------------------
		
		// define hot keys
		m_TabsPane.addKeyListener( getArrowKeyListener() );
        
		// for drag'n'drop feature
        new FileDrop(this, new FileDrop.Listener() {
			public void filesDropped(final File[] files) {
				if(files.length > 1) {
					String msg = "Hey du Workaholic!\n=> Immer schön eins nach dem anderen... ;)";
					JOptionPane.showMessageDialog(Demo.this, msg);
				} else {
					Thread openFileThread = new Thread(new Runnable() {
						@Override
						public void run() {
							checkFile(files[0]);
						}
					});
					openFileThread.start();
				}
			}
        });
        
        // window should be centered on the primary monitor
        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
        int posX = (int)(dim.getWidth()-DEF_WIDTH) / 2;
        int posY = (int)(dim.getHeight()-DEF_HEIGHT) / 2;
        
        this.setSize(DEF_WIDTH, DEF_HEIGHT);
        this.setLocation(posX, posY);

        // defining close operation
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

	public KeyListener getArrowKeyListener() {
		if(m_keyListener == null) {
			m_keyListener = new KeyListener() {
				@Override public void keyTyped(KeyEvent ev) {}
				@Override public void keyReleased(KeyEvent ev) {}
				@Override
				public void keyPressed(KeyEvent ev) {
					switch(ev.getKeyCode()) {
						case KeyEvent.VK_LEFT:	Demo.this.prevTab();
						case KeyEvent.VK_RIGHT:	Demo.this.nextTab();
					}
				}
			};
		}
		return m_keyListener;
	}

	
    //========================================================================================

	
	// previous tab
	public void prevTab() {
		int index = m_TabsPane.getSelectedIndex();
		
		if(index > 0) {
			m_TabsPane.setSelectedIndex(index-1);
		}
	}
	
	// next tab
	public void nextTab() {
		int index = m_TabsPane.getSelectedIndex();
		
		if(index < m_TabsPane.getTabCount()-1) {
			m_TabsPane.setSelectedIndex(index);
		}
	}
	
	// loading file
    private void loadFile(File logFile) {
		if(logFile != null) {
			this.setTitle( APP_TITLE + ": beim Laden..." );
			final MyList newList = new MyList();
			m_TabsPane.add(logFile.getName(), newList);
			m_TabsPane.setSelectedComponent(newList);
			
			final double factor = (double)logFile.length() / 100;			

			try {
				m_progress.setValue(0);
				m_progress.setVisible(true);
				
				BufferedReader in = new BufferedReader(new FileReader(logFile));
	    		String line       = null;
	    		double percent    = 0;
	    		
	    		while((line = in.readLine()) != null) {
	    			percent += ((line.length() + 2) / factor);
	    			newList.addItem(line);	    	    				
    				m_progress.setValue( (int)percent );
  	 			}
	    		
	    		m_progress.setVisible(false);
			} catch(Exception ex) {
				String msg = "Beim Lesen der Datei ist folgender Fehler aufgetreten:\n" + ex.toString();
				JOptionPane.showMessageDialog(this, msg);
				return;
			}
			
			this.setTitle(APP_TITLE);
		}
    }
    
    private void checkFile(final File logFile) {
		if(logFile != null) {
			int ret = JOptionPane.showOptionDialog(this, "Soll das Fenster angezeigt werden.", "Test", 0, 0, null, null, null);
			
			if(ret == 0) {
				MyWindow win = MyWindow.getInstance();
				win.addComponentListener(new ComponentListener() {
					@Override public void componentMoved(ComponentEvent arg0) {}
					@Override public void componentResized(ComponentEvent arg0) {}
					@Override public void componentShown(ComponentEvent arg0) {}
					@Override
					public void componentHidden(ComponentEvent arg0) {
						loadFile(logFile);
					}
				});
				win.addWindowListener(new WindowListener() {
					@Override public void windowActivated(WindowEvent arg0) {}
					@Override public void windowClosing(WindowEvent arg0) {}
					@Override public void windowDeactivated(WindowEvent arg0) {}
					@Override public void windowDeiconified(WindowEvent arg0) {}
					@Override public void windowIconified(WindowEvent arg0) {}
					@Override public void windowOpened(WindowEvent arg0) {}
					@Override public void windowClosed(WindowEvent arg0) {
						loadFile(logFile);
					}
				});
				win.setVisible(true);
			} else {
				loadFile(logFile);
			}
		}
    }   

    //========================================================================================

    
    // singleton
    public static Demo getInstance() {
    	if(m_instance == null) {
    		m_instance = new Demo();
    	}
    	return m_instance;
    }
    
    // main routine
    public static void main(final String[] arg) throws IOException {
		System.out.println("Java-Version: " + System.getProperty("java.version"));

		try {
			UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
		}
		catch (ClassNotFoundException e) { e.printStackTrace(); }
		catch (InstantiationException e) { e.printStackTrace(); }
		catch (IllegalAccessException e) { e.printStackTrace(); }
		catch (UnsupportedLookAndFeelException e) { e.printStackTrace(); }

        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                	Demo win = Demo.getInstance();
                    win.setVisible(true);
                    
            		if(arg.length > 0) {
            			win.loadFile(new File(arg[0]));
            		} else {
            			win.setTitle(APP_TITLE + ": Datei einfach hereinziehen");
            		}
                } catch(Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
    }
}



@SuppressWarnings("serial")
class MyList extends JScrollPane {

	private JList m_listComponent = null;
	private DefaultListModel m_listModel = null;
	
	public MyList() {
		// Add listbox with the ability to handle tabulator characters 
		m_listComponent = new JList();		
		super.add(m_listComponent);
		
		// Define list model
		m_listModel = new DefaultListModel();
		m_listComponent.setModel(m_listModel);

		// Define viewport for the JScrollPane (this class is extending JScrollPane)
		this.setViewportView(m_listComponent);
	}
	
	public void addItem(Object obj) {
		final Object finalObj = obj;
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				m_listModel.addElement(finalObj);
			}
		});
	}
}


@SuppressWarnings("serial")
class MyWindow extends JDialog {
	private static MyWindow m_instance = null;
	
	private MyWindow() {
		super(Demo.getInstance(), false);
		
		JButton but1 = new JButton("Ausblenden");
		but1.setPreferredSize(new Dimension(100, 20));
		but1.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				MyWindow.this.setVisible(false);
			}
		});
		JButton but2 = new JButton("Schließen");
		but2.setPreferredSize(new Dimension(100, 20));
		but2.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				MyWindow.this.dispose();
			}
		});
		
		this.setLayout( new FlowLayout() );
		this.add(but1);
		this.add(but2);
		this.pack();
		this.setVisible(true);
		centerTo(Demo.getInstance());
	}
	
	public static MyWindow getInstance() {
		if(m_instance == null) {
			m_instance = new MyWindow();
		}
		return m_instance;
	}
	
	private void centerTo(JFrame owner) {
		int x = owner.getLocation().x + (owner.getWidth() - this.getWidth()) / 2;
		int y = owner.getLocation().y + (owner.getHeight() - this.getHeight()) / 2;
		
		this.setLocation(x, y);
	}
}
Um die Probleme zu sehen, solltet ihr eine möglichst große (~10...50MB) Textdatei in das Hauptfenster ziehen. Dann erscheint eine Abfrage, ob ein Fenster angezeigt werden soll. Bitte Nein ankicken, um das erste Problem darzustellen. Wenn Ja angeklickt wird, wird ein weiterer Dialog angezeigt. Dieser kann dann nur ausgeblendet (setVisible), oder aber ganz geschlossen (dispose) werden - egal was man wählt, die Aktualisierung des UI erfolgt erst mit Abschluss des Ladevorgangs.

Ich füge diesem Dialog einen Component/WindowListener hinzu, in welchem der Ladevorgang gestartet wird, sobald der Nutzer eine Auswahl getroffen und somit den Dialog geschlossen hat. - Kann es sein, dass damit der Ladevorgang selber im EDT läuft? Wie kann man so etwas besser machen?

Vielen Dank für eure Unterstützung! 🙂
 
"percent += ((line.length() + 2) / factor);" Was willst du denn damit ausrechnen? Eine Fortschrittsanzeige wird daraus jedenfalls nicht. Ich hab dir in deinem anderen Thread ein Beispiel reingestellt wo das eigentlich erklärt sein sollte. Mehrere Threads für das gleiche Problem zu öffnen wird normalerweise nicht gern gesehen.
 
"percent += ((line.length() + 2) / factor);" Was willst du denn damit ausrechnen? Eine Fortschrittsanzeige wird daraus jedenfalls nicht. Ich hab dir in deinem anderen Thread ein Beispiel reingestellt wo das eigentlich erklärt sein sollte.
Ja, das hat mir sehr geholfen. Danke.

Die Formel passt:
factor ist die Dateigröße geteilt durch 100 (da die JProgressBar standardmäßig auf einen Maximalwert von 100 eingestellt ist).
In der Formel addiere ich die Längen der einzelnen Zeilen aus der Datei zusammen und dividiere das ganze durch factor, um den aktuellen Prozentwert zu erhalten.
Die +2 ist für den Zeilenumbruch unter Windows. - Gibt es evtl. eine Möglichkeit, über den BufferedReader die Art des Zeilenumbruchs herauszufinden? Ich fände es irgendwie hässlich, die Datei zunächst daraufhin zu untersuchen und anschließend erneut für das zeilenweise Lesen zu öffnen...

Mehrere Threads für das gleiche Problem zu öffnen wird normalerweise nicht gern gesehen.
Im anderen Thread ging es anfangs nur um die Fortschrittsanzeige, das Newline-Problem (siehe Frage vorher) und um ein KeyListener/KeyBinding-Problem. Hier geht es eher um das Problem der Aktualisierung, wenn ich zuvor ein Fenster öffene. - Ich habe auch schon eine Vermutung geäußert, woran das liegen könnte. Allerdings habe ich keine Idee, wie ich es anders machen kann, wenn der Dialog unbedingt modeless sein soll. Um das ganze besser zu verdeutlichen habe ich eine Demo gemacht, welche auch die Darstellung des Progressbar-Problems hergibt, weshalb ich nun an dieser Stelle nochmals darauf eingegangen bin.

[TIPP]Das Problem mit der Fortschrittsanzeige konnte durch bERt0r bereits gelöst werden. Vielen Dank hierfür.[/TIPP]
 
Das stimmt aber nicht. Um beim 20. Teil von 200 auf die 10% zu kommen musst du 20 / 200 * 100 rechnen. Nicht 20 / 200 / 100.
Ich bin mir ziemlich sicher, dass es funktioniert, da es immer genau bis 100% läuft.
Wenn ich die +2 weg lasse, dann bleibt die Fortschrittsanzeige immer ein Stück vor 100% stehen.

Die Rechnung ist folgende:

200MB / 100% = 2MB/%
20MB / (2MB/%) = 10%
 

Zurück
Oben