Ausgabe von Zwischenergebnissen in einem Textfeld

Igor1312

Mitglied
Hi,

ich habe mit Netbeans eine Windows Desktopanwendung generiert. In der View-Klasse, dem sichtbaren Teil der Anwendung, ist ein Textfeld und ein Button. Der Button startet eine Prozedur, die von 1 bis 10 zählt und zwischendurch je 1 Sekunde wartet. Die Zahlen werden in dem Textfeld ausgegeben (textfeld.append()).

Wenn ich das ohne Schnickschnack programmiere, erscheint die Ausgabe 1..10 erst, wenn die Prozedur beendet ist und die "Kontrolle" wieder die View-Klasse hat.

Wie schicke ich die Prozedur in den Hintergrund (Swing, Runnable)?

Netbeans bietet da eine Lösung.
Hier der von Netbeans generierte Code der Prozedur, wenn man den Haken bei "Hintergrundaufgabe" setzt:
Code:
/**
 * Zählen und warten
 */
@Action public Task machet() {
        return new MachetTask(org.jdesktop.application.Application.getInstance(machwas.MachwasApp.class));
}

    private class MachetTask extends org.jdesktop.application.Task<Object, Void> {
        MachetTask(org.jdesktop.application.Application app) {
            // Runs on the EDT.  Copy GUI state that
            // doInBackground() depends on from parameters
            // to MachetTask fields, here.
            super(app);
        }
        @Override protected Object doInBackground() {
            // Your Task's code here.  This method runs
            // on a background thread, so don't reference
            // the Swing GUI from here.
            for (int i=1;i<10;i++){
                System.out.println("Zahl: " + i);
                myView.setTextfeld("Zahl: "+ i + "\n");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    Logger.getLogger(MachwasApp.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

            return null;  // return your result
        }
        @Override protected void succeeded(Object result) {
            // Runs on the EDT.  Update the GUI based on
            // the result computed by doInBackground().
        }
    }

Soweit so gut.

1. Problem: Wenn man den Button bei 4 nochmal drückt, laufen zwei Tasks durcheinander ...
Ich möchte aber mit der Ausführung warten, bis der erste fertig ist. Es geht mir hier in erster Linie um die Ausgabe von Zwischenergebnissen im Textfeld, nicht um die Ausführung im Hintergrund.

2. Problem:
Ich möchte die Schleife parametrisieren, also beim Druck auf den Button soll die GUI der Prozedur einen Parameter für die Anzahl der Schleifendurchläufe mitgeben.
Der freundliche Hinweis von Netbeans
// Runs on the EDT. Copy GUI state that
// doInBackground() depends on from parameters
// to MachetTask fields, here.
soll mir wohl sagen, was zu tun ist, aber ich verstehe das nicht. Und das liegt nicht an meinen Englischkenntnissen. Wie kopiere ich Parameter in die Task-Fields? Und wie bzw. wo übergebe ich Parameter an MachetTask() ?

Vielen Dank für jede Hilfe!

Tschö,
Igor
 
Zuletzt bearbeitet:
Keiner hat eine Idee dazu?
Ich würde mich schon über Links zu hilfreichen Themen, von mir aus auch in anderen Foren oder auch nur über die richtigen Suchbegriffe freuen, damit ich was zu dem "Problem" finde ... Ich bin nämlich nicht nur neu, was Java Programmierung angeht, sondern auch Windows GUI ist Neuland.

Kann doch nicht angehen, dass man mit Java keine Zwischenstände von Berechnungen ausgeben kann, um den Benutzer zu informieren. Strikt threadsafe oder was auch immer hin oder her.

Tschö,
Igor
 
1. Problem: Wenn man den Button bei 4 nochmal drückt, laufen zwei Tasks durcheinander ...
Ich möchte aber mit der Ausführung warten, bis der erste fertig ist. Es geht mir hier in erster Linie um die Ausgabe von Zwischenergebnissen im Textfeld, nicht um die Ausführung im Hintergrund.

Du könntest am Anfang deiner Prozedur deinen Button ausschalten und ihn am Ende deiner Prozedur wieder anschalten.

2. Problem:
Ich möchte die Schleife parametrisieren, also beim Druck auf den Button soll die GUI der Prozedur einen Parameter für die Anzahl der Schleifendurchläufe mitgeben.
Der freundliche Hinweis von Netbeans
// Runs on the EDT. Copy GUI state that
// doInBackground() depends on from parameters
// to MachetTask fields, here.
soll mir wohl sagen, was zu tun ist, aber ich verstehe das nicht. Und das liegt nicht an meinen Englischkenntnissen. Wie kopiere ich Parameter in die Task-Fields? Und wie bzw. wo übergebe ich Parameter an MachetTask() ?

Ich bin mir nicht sicher, ob ich es richtig verstanden hab, was du willst. Aber du könntest in deiner Klasse eine Variable deklarieren, die du vor deiner Prozedur änderst und auf welche deine Prozedur dann zugreift.
 
Hallo HonniCilest,

vielen Dank für deine Antwort.
Leider geht deine Lösung am Problem vorbei.

zu 1. Es geht nicht darum, dass diese Prozedur nur einmal laufen soll. Die gesamte Verarbeitung dse Programmes soll warten, bis der Task zu Ende ist. Es geht es mir im Grunde nur darum, das Textfeld, welches sich im GUI Task befindet aus einem anderen Task heraus zu aktualisieren. Das Referenzieren ist kein Problem. Aber das Textfeld wird eben erst dann aktualisiert, wenn der GUI Thread wieder den Fokus erhält, also nach Ausführung der Prozedur.

zu 2. Ich möchte gerade nicht mit globalen (Klassen-)Variablen arbeiten, sondern mit Parametern. Wie geht das mit diesem Netbeans-Template? Ich verstehe die Kommentare im generierten Code nicht, bzw. weiß nicht wie ich es umsetzen muss.

Tschö,
Igor
 
Hallo,

erstens wie du es so schön sagst, dein GUI-Thread hat den Focus. ;-)
Dein Task bzw das Event des Buttons wird genau in diesem Thread ausgeführt. Desalb kann natürlich auch nicht gezeichnet werden.

Das heißt du musst das ganz in einen extra Thread ausladen. Da du das ganze dennoch nacheinander ausführen möchtest, würde sich ein SingleThreadExecutor anbieten.

Code:
Executor executor = Executors.newSingleThreadExecutor();

So wird nur ein Thread gleichzeitig ausgführt.

EDIT:
Ups, ich sehe du hast ja die Task-Klasse des ApplicationFrameworks. Da wird es ja angeblich im Hintergrund ausgeführt.

Deine Befehle musst du dann in ein Runnable schreiben. Dieses kannst du dann mit submit im executor ausführen.

Gruß

Sascha
 
Hi Sascha,

vielen Dank für deine Antwort.
erstens wie du es so schön sagst, dein GUI-Thread hat den Focus.
Dein Task bzw das Event des Buttons wird genau in diesem Thread ausgeführt. Desalb kann natürlich auch nicht gezeichnet werden.

Ich finde es gar nicht natürlich, weil wenn der Thread den Fokus hat, wieso kann er dann seine Steuerelemente nicht aktualisieren?

Ansonsten verstehe ich leider nichts von dem, was du schreibst. Ist es möglich, dass du das mit ein paar Zeilen Code ausmalst, den ich zum Testen übernehmen kann? Ich bin leider blutiger Anfänger in Java UND Windows-Programmierung :(
Netbeans hat ja schon ne ganze Menge hübschen Code generiert, aber der tut's nicht so, wie ich es gerne hätte. Wie muss ich den denn erweitern/ändern, damit genau das passiert, was ich beschrieben habe? Ich meine da insbesondere die Parameter-Geschichte, denn das Aktualisieren klappt ja mit dem Beispiel ganz prima, weil der Task machet() im Hintergrund läuft.

Im Grunde ist dieses Problem doch ein Standard-Problem der GUI Programmierung, oder täusche ich mich? Das kann doch einfach nicht so kompliziert sein, dass einer wie ich es nicht kapiert! Auch wenn ich neu in Java und GUI bin, so bin ich nicht gerade begriffsstutzig.

Tschö,
Igor
 
Ich hatte übersehen, dass du das ApplicationFramework nutzt.
Da verhält sich das ganze ein bisschen anders.

Hier ist eine kleine BeispielApplication. Die eigentlich das machen sollte was du möchtest.
Java:
/*
 * DesktopApplication2View.java
 */

package desktopapplication2;

import java.util.logging.Level;
import java.util.logging.Logger;
import org.jdesktop.application.Action;
import org.jdesktop.application.ResourceMap;
import org.jdesktop.application.SingleFrameApplication;
import org.jdesktop.application.FrameView;
import org.jdesktop.application.Task;
import org.jdesktop.application.TaskMonitor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
import javax.swing.Icon;
import javax.swing.JDialog;
import javax.swing.JFrame;

/**
 * The application's main frame.
 */
public class DesktopApplication2View extends FrameView {

    public DesktopApplication2View(SingleFrameApplication app) {
        super(app);

        initComponents();

        // status bar initialization - message timeout, idle icon and busy animation, etc
        ResourceMap resourceMap = getResourceMap();
        int messageTimeout = resourceMap.getInteger("StatusBar.messageTimeout");
        messageTimer = new Timer(messageTimeout, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                statusMessageLabel.setText("");
            }
        });
        messageTimer.setRepeats(false);
        int busyAnimationRate = resourceMap.getInteger("StatusBar.busyAnimationRate");
        for (int i = 0; i < busyIcons.length; i++) {
            busyIcons[i] = resourceMap.getIcon("StatusBar.busyIcons[" + i + "]");
        }
        busyIconTimer = new Timer(busyAnimationRate, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                busyIconIndex = (busyIconIndex + 1) % busyIcons.length;
                statusAnimationLabel.setIcon(busyIcons[busyIconIndex]);
            }
        });
        idleIcon = resourceMap.getIcon("StatusBar.idleIcon");
        statusAnimationLabel.setIcon(idleIcon);
        progressBar.setVisible(false);

        // connecting action tasks to status bar via TaskMonitor
        TaskMonitor taskMonitor = new TaskMonitor(getApplication().getContext());
        taskMonitor.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
            public void propertyChange(java.beans.PropertyChangeEvent evt) {
                String propertyName = evt.getPropertyName();
                if ("started".equals(propertyName)) {
                    if (!busyIconTimer.isRunning()) {
                        statusAnimationLabel.setIcon(busyIcons[0]);
                        busyIconIndex = 0;
                        busyIconTimer.start();
                    }
                    progressBar.setVisible(true);
                    progressBar.setIndeterminate(true);
                } else if ("done".equals(propertyName)) {
                    busyIconTimer.stop();
                    statusAnimationLabel.setIcon(idleIcon);
                    progressBar.setVisible(false);
                    progressBar.setValue(0);
                } else if ("message".equals(propertyName)) {
                    String text = (String)(evt.getNewValue());
                    statusMessageLabel.setText((text == null) ? "" : text);
                    messageTimer.restart();
                } else if ("progress".equals(propertyName)) {
                    int value = (Integer)(evt.getNewValue());
                    progressBar.setVisible(true);
                    progressBar.setIndeterminate(false);
                    progressBar.setValue(value);
                }
            }
        });
    }

    @Action
    public void showAboutBox() {
        if (aboutBox == null) {
            JFrame mainFrame = DesktopApplication2.getApplication().getMainFrame();
            aboutBox = new DesktopApplication2AboutBox(mainFrame);
            aboutBox.setLocationRelativeTo(mainFrame);
        }
        DesktopApplication2.getApplication().show(aboutBox);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">
    private void initComponents() {

        mainPanel = new javax.swing.JPanel();
        jTextField1 = new javax.swing.JTextField();
        jButton1 = new javax.swing.JButton();
        menuBar = new javax.swing.JMenuBar();
        javax.swing.JMenu fileMenu = new javax.swing.JMenu();
        javax.swing.JMenuItem exitMenuItem = new javax.swing.JMenuItem();
        javax.swing.JMenu helpMenu = new javax.swing.JMenu();
        javax.swing.JMenuItem aboutMenuItem = new javax.swing.JMenuItem();
        statusPanel = new javax.swing.JPanel();
        javax.swing.JSeparator statusPanelSeparator = new javax.swing.JSeparator();
        statusMessageLabel = new javax.swing.JLabel();
        statusAnimationLabel = new javax.swing.JLabel();
        progressBar = new javax.swing.JProgressBar();

        mainPanel.setName("mainPanel"); // NOI18N

        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(desktopapplication2.DesktopApplication2.class).getContext().getResourceMap(DesktopApplication2View.class);
        jTextField1.setText(resourceMap.getString("jTextField1.text")); // NOI18N
        jTextField1.setName("jTextField1"); // NOI18N

        javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(desktopapplication2.DesktopApplication2.class).getContext().getActionMap(DesktopApplication2View.class, this);
        jButton1.setAction(actionMap.get("doInBackGround")); // NOI18N
        jButton1.setName("jButton1"); // NOI18N

        org.jdesktop.layout.GroupLayout mainPanelLayout = new org.jdesktop.layout.GroupLayout(mainPanel);
        mainPanel.setLayout(mainPanelLayout);
        mainPanelLayout.setHorizontalGroup(
            mainPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(mainPanelLayout.createSequentialGroup()
                .add(55, 55, 55)
                .add(mainPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(jButton1)
                    .add(jTextField1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(240, Short.MAX_VALUE))
        );
        mainPanelLayout.setVerticalGroup(
            mainPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(mainPanelLayout.createSequentialGroup()
                .addContainerGap()
                .add(jTextField1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .add(28, 28, 28)
                .add(jButton1)
                .addContainerGap(136, Short.MAX_VALUE))
        );

        menuBar.setName("menuBar"); // NOI18N

        fileMenu.setText(resourceMap.getString("fileMenu.text")); // NOI18N
        fileMenu.setName("fileMenu"); // NOI18N

        exitMenuItem.setAction(actionMap.get("quit")); // NOI18N
        exitMenuItem.setName("exitMenuItem"); // NOI18N
        fileMenu.add(exitMenuItem);

        menuBar.add(fileMenu);

        helpMenu.setText(resourceMap.getString("helpMenu.text")); // NOI18N
        helpMenu.setName("helpMenu"); // NOI18N

        aboutMenuItem.setAction(actionMap.get("showAboutBox")); // NOI18N
        aboutMenuItem.setName("aboutMenuItem"); // NOI18N
        helpMenu.add(aboutMenuItem);

        menuBar.add(helpMenu);

        statusPanel.setName("statusPanel"); // NOI18N

        statusPanelSeparator.setName("statusPanelSeparator"); // NOI18N

        statusMessageLabel.setName("statusMessageLabel"); // NOI18N

        statusAnimationLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
        statusAnimationLabel.setName("statusAnimationLabel"); // NOI18N

        progressBar.setName("progressBar"); // NOI18N

        org.jdesktop.layout.GroupLayout statusPanelLayout = new org.jdesktop.layout.GroupLayout(statusPanel);
        statusPanel.setLayout(statusPanelLayout);
        statusPanelLayout.setHorizontalGroup(
            statusPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(statusPanelSeparator, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
            .add(statusPanelLayout.createSequentialGroup()
                .addContainerGap()
                .add(statusMessageLabel)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, 204, Short.MAX_VALUE)
                .add(progressBar, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(statusAnimationLabel)
                .addContainerGap())
        );
        statusPanelLayout.setVerticalGroup(
            statusPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(statusPanelLayout.createSequentialGroup()
                .add(statusPanelSeparator, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .add(statusPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(statusMessageLabel)
                    .add(statusAnimationLabel)
                    .add(progressBar, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .add(3, 3, 3))
        );

        setComponent(mainPanel);
        setMenuBar(menuBar);
        setStatusBar(statusPanel);
    }// </editor-fold>

    @Action
    public Task doInBackGround() {
        return new DoInBackGroundTask(getApplication());
    }

    private class DoInBackGroundTask extends org.jdesktop.application.Task<Object, Void> {
        DoInBackGroundTask(org.jdesktop.application.Application app) {
            // Runs on the EDT.  Copy GUI state that
            // doInBackground() depends on from parameters
            // to DoInBackGroundTask fields, here.
            super(app);
            System.out.println("test");
        }


        @Override protected Object doInBackground() {
            while(lock)
                try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {

            }
            lock = true;
             for (int i=1;i<10;i++){
                System.out.println("Zahl: " + i);
                jTextField1.setText("Zahl: "+ i + "\n");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {

                }
            }

            return null;  //
        }
        @Override protected void succeeded(Object result) {
            lock = false;
        }
    }


    // Variables declaration - do not modify
    private javax.swing.JButton jButton1;
    private javax.swing.JTextField jTextField1;
    private javax.swing.JPanel mainPanel;
    private javax.swing.JMenuBar menuBar;
    private javax.swing.JProgressBar progressBar;
    private javax.swing.JLabel statusAnimationLabel;
    private javax.swing.JLabel statusMessageLabel;
    private javax.swing.JPanel statusPanel;
    // End of variables declaration
    private boolean lock = false;
    private final Timer messageTimer;
    private final Timer busyIconTimer;
    private final Icon idleIcon;
    private final Icon[] busyIcons = new Icon[15];
    private int busyIconIndex = 0;

    private JDialog aboutBox;
}
 
Hi Sascha,

danke, das ist super!

Ich habe es bei der Aufteilung des Codes in View- und App-Klasse (EDT) gelassen und den ganzen doInBackground-Kram in die App-Klasse schreiben lassen. Macht keinen Unterschied, läuft!

Der Code ist lustigerweise fast genau der, den ich gepostet habe, den Netbeans automatisch generiert hat.
Zusätzlich hast du eine globale Variable lock und eine While-Schleife eingefügt und setzst die Variable in succeed() auf false.
Und das macht den Unterschied :)

Beim 2. Klick wartet der Task also darauf, dass der Vorgängertask lock=false setzt und läuft erst dann weiter. Ich habe statt while (lock) {} nur "if (lock) return null;" geschrieben. Dann werden die Buttonklicks während der Ausführung des Tasks quasi ignoriert. Ich möchte es so ;)

Ich habe vor die While-Schleife noch "int z = myView.getZaehler();" geschrieben. getZaehler() ist ein static Getter für ein Textfeld, in dem die Anzahl der Schleifendurchläufe steht. Mit deinem while(lock) passiert folgendes, wenn man den Button 2x klickt, dann den Wert ändert und ein drittes Mal klickt. Es ist passiert, dass der 3. Task vor dem 2. gestartet wurde und mit dem neuen Wert des Textfeldes arbeitet. Dann erst lief der 2. Task mit dem alten Wert. Auf ei Reihenfolge der Taskabarbeitung hat man wohl keinen Einfluss. Aber das ist im Moment nicht mein Problem, ich hab ja eine Lösung dagegen.

Wenn man nun einen zweiten Button einbaut, der einen Vordergrund-Task startet, wird das richtig amüsant. Die Ausgabe wird wieder angehalten (im System.out sieht man aber, dass und wie es durcheinanderläuft) und am Ende stehen die Ergebnisse der Zählschleifen durcheinander. Nachvollziehbar, aber wenig deterministisch. Man muss also wenn schon immer mit lock arbeiten.

Zum Schluss noch eine Frage.
Was bedeutet das hier:
// Runs on the EDT. Copy GUI state that
// doInBackground() depends on from parameters
// to DoInBackGroundTask fields, here.
Ich übersetze das so: "Läuft im EventDispatchingThread (nicht die GUI, sondern die AppKlasse). Kopiere an dieser Stelle den GUI Status, also das, wovon doInBackground() abhängt, von den Parametern in die Felder des DoInBackGroundTask."
Hier erfolgt also die von mir gewünschte Parameter-Übergabe und ich kann mir die getText-Methode sparen. Aber wie ist die Syntax dafür?

Und dann hab ich noch eine Frage, aber dafür mache ich jetzt einen neuen Thread auf.

Tschö,
Igor
 
Hi,

ich hab das Problem mit der Parameterübergabe nun gelöst, mit einem weiteren Konstruktor:

Code:
 private class Machet_bTask extends Task<Object, Void> {

        /** Anzahl der Iterationen */
        private int iterations = 0;

        /** Erweiterter Konstruktor mit zusätzlichem Parameter */
        public Machet_bTask(Application app, int iterations) {
            // Runs on the EDT.  Copy GUI state that
            // doInBackground() depends on from parameters
            // to Machet_in_backTask fields, here.
            super(app);
            this.iterations = iterations;
        }
[...]

Der Aufruf über die @Action Methode:

Code:
@Action
  public Task machet_b() {
        return new Machet_bTask(getApplication(), myView.getZaehler());
    }

myView .getZaehler() liefert den Wert aus einem Textfeld, in dem die Schleifendurchläufe stehen.

Tschö,
Igor
 
Zuletzt bearbeitet:
Zurück