Verspätete Anzeige in Textfeldern

Diskutiere Verspätete Anzeige in Textfeldern im AWT, Swing, JavaFX & SWT Bereich.
kodela

kodela

Hallo,

ich arbeite an einem Schachprogramm und habe folgendes Problem:

Wird vom Anwender ein gültiger Zug eingegeben, dann soll dieser Zug, einschließlich der dafür benötigten Denkzeit, im Info-Bereich ausgegeben werden. Außerdem soll in diesem Bereich die Farbe der nun am Zug befindlichen Seite angezeigt werden. Im Anschluss daran soll das Programm einen Gegenzug suchen und ausführen, es sei denn, die Gegenzugsuche ist explizit ausgeschlossen.

Aus mir unerklärlichen Gründen werden die genannten Infos jedoch nicht unmittelbar nach der Eingabe eines Zuges angezeigt, sondern erst nach Abschluss der Gegenzugsuche. Ausgegeben wird jedoch der Zug selbst, also der Wechsel einer Figur von einem zu einem anderen Feld.

Der Anzeigebereich der Anwendung ist zweigeteilt, einmal ein JPanel für das Schachfeld und eines für das Infofeld. Im Infofeld sind eine Reihe von JTextfields für die diversen Anzeigen.

Hier ein kommentierter Code-Auszug der betroffenen Methoden:
Java:
Class InOut:

    private void movePiece(int i) {
        int color = board.Display[i].color;                         // Farbe der Figur
        if (board.Display[i].piece == 0) {                          // keine Figur, zweiter Klick
            color = -1;                                             // dann leeres Zielfeld
        }
        if ((lastFild.piece > 0)                                    // bereits Figur markiert
                && (talk.Player != color)) {                        // keine eigene Figur angeklickt
            if (talk.MoveCheck(lastIndex, i)) {                     // Zug prüfen, falls erfolgreich
           
// Bei korrektem Zug ist nun bereits die Anzeige des Zuges und des nächsten Players veranlasst.
// Diese Infos werden jedoch noch nicht angezeigt.

                if (cApp.Level < L_twoplayer) {                     // Level mit Zugsuche
                    talk.startMove();                               // Zugsuche einleiten
                }
            } else {                                                // Zug ist ungültig
                PrintPiece(                                         // also Markierung entfernen
                        lastIndex, lastFild.piece, lastFild.color);
            }
        }
        resetInput();                                               // zurücksetzen für nächsten Zug
    }

   
Class Talk:

// Nach dem Aufruf von talk.MoveCheck() wird bei einem gültigen Zug diese Methode aufgerufen.

    void enterMove(MOT move) {
        disp.PrintMove(MoveNo, Player,           // >> Zug im Infofenster ausgeben <<
                move, tm.getTotalTime(Player));
        MakeMove(move);                                             // Zug formal ausführen
        board.UpdateBoard();                                        // Brett neu ausgeben
        cApp.showColorToPlay(Player);            // >> Ziehenden im Info-Fenster <<
        PrintComment();                                             // ev. erf. Meldung ausgeben
    }
   
// Die Anzeige des Zuges, einschließlich der dafür benötigten Zeit erfolgt über die Klasse Display.
// Die Anzeige des nun am Zug befindlichen Spielers direkt über die Hauptklasse ChessApp.
// Anschließend erfolgt der Rücksprung  zu Methode InOut.movePiece().  Die veranlassten Infos
// werden, wenn jetzt durch den Aufruf von talk.StartMove() die Suche eines Gegenzuges eingeleitet
// wird, noch nicht ausgegeben. Sie erfolgt jedoch, wenn auf Grund der Leveleinstellung der Aufruf
// von talk.StartMove() übersprungen wird.


Class Display:

// Der Vollständigkeit halber hier noch die Methode PrintMove() für die Ausgabe des Zuges.

    public void PrintMove(int moveno, int color, MOT move, int time) {
        printThinkTime(color, time);                                // Zeit ausgeben
        String sMove = "" + (moveno/2+1) + "." + MoveStr(move);        // String für den Zug bilden
        if (color == WHITE) {                                        // für Weiß?
            cApp.tfLastMoveWhite.setText(sMove);                    // dann in Feld für Weiß
        } else {
            cApp.tfLastMoveBlack.setText(sMove);                    // sonst in Feld für Schwarz
        }
    }
Ich habe es schon mit einem Aufruf von repaint() (cApp.repaint() / cApp.infoPain.repaint()) an unterschiedlichen Stellen versucht, leider jedoch ohne Erfolg.

Hat jemand eine Idee, warum der Aufruf von talk.startMove() die sofortige Anzeige der Infos verhindert?

Danke schon einmal für jede Antwort.

Gruß, kodela
 
L

LimDul

In Swing gibt es einen Thread (den Event Dispatching Thread), der für alles verantwortlich ist - abarbeiten der Events, Zeichnen etc.

Das heißt, so lange in in Code, der z.B. aus einem ActionListener aufgerufen wird, bist, ist die gesamte Gui blockiert. Erst wenn das abgearbeitet ist, geht es weiter. Deswegen darf man in diesem Code nie folgende Dinge tun:
* Thread.sleep
* Sonstige langlaufende Aktionen

Wenn man sowas tun will, muss man das in eigene Threads auslagern (Die dürfen dann aber keine Gui-Element direkt manipulieren, sondern nur mittels SwingUtils.invokeAndWait bzw. invokeLater)
 
kodela

kodela

Danke für die Antwort!

Ich habe mittlerweile festgestellt, dass mein Problem am MouseListener liegt, von dem aus myMouseClicked() und weiter dann movePiece() aufgerufen wird. Erst wenn von dort der Rücksprung erfolgt, wird der aufgerufene MouseListener verlassen und damit die Anzeige freigegeben.

Was müsste ich tun, dass diese Blockierung umgangen werden kann? Durch die Aufrufe von talk.MoveCheck und talk.startMove(); wird auf weitere Klassen (Search und Evalu) zugegriffen. Die kann ich ja nicht alle in eigene Threads legen, oder doch?

Warum wird dann aber die Figur gezogen?
 
L

LimDul

Danke für die Antwort!

Ich habe mittlerweile festgestellt, dass mein Problem am MouseListener liegt, von dem aus myMouseClicked() und weiter dann movePiece() aufgerufen wird. Erst wenn von dort der Rücksprung erfolgt, wird der aufgerufene MouseListener verlassen und damit die Anzeige freigegeben.

Was müsste ich tun, dass diese Blockierung umgangen werden kann? Durch die Aufrufe von talk.MoveCheck und talk.startMove(); wird auf weitere Klassen (Search und Evalu) zugegriffen. Die kann ich ja nicht alle in eigene Threads legen, oder doch?

Warum wird dann aber die Figur gezogen?
Natürlich, du kannst ja die Objekte mit übergeben beim erzeugen des Threads.
 
kodela

kodela

Ich habe jetzt die Search-Klasse in einen eigenen Threas gepackt, was sowieso geplant war. Trotzdem bekomme ich die Infos zum Zug, für den in Search der Gegenzug gesucht wird, nicht angezeigt.

Was ich einfach nicht verstehe ist, dass der Zug selbst auf dem Brett angezeigt wird, die Infos dagegen nicht. Irgendwie sind bei mir da die Weichen für meine Gedankengänge falsch gestellt.
 
mihe7

mihe7

Ohne Code kann man da nicht viel sagen, außer, dass die Updates im EDT stattfinden müssen. D. h. wenn Du z. B. in einem separaten Thread das GUI aktualisieren willst, musst Du invokerLater verwenden.
 
kodela

kodela

Ja, das ist mir schon klar, dass es ohne den ganzen Code zu kennen, schwierig ist, dazu etwas zu sagen. Andererseits ist es nahezu unmöglich, hier den gesamten Code zu zeigen, der im Zusammenhang mit meinem Problem ausgeführt wird. Deshalb habe ich mich in meinem ersten Beitrag bemüht, die wichtigsten Codeteile zu zeigen. Ich will aber versuchen, Grundzüge des Projekts und in besonderem Maße die problemrelevanten Teile kurz darzustellen:

Zur Zeit besteht das Projekt aus zwanzig Klassen. Die Hauptklasse ist "ChessApp" mit der main-Methode:

Code:
    public static void main(final String args[]) {
        try {
            UIManager.setLookAndFeel(
                    UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException
                | InstantiationException
                | IllegalAccessException
                | UnsupportedLookAndFeelException ex) {
            JOptionPane.showMessageDialog(null,
                    "Folgender Fehler ist aufgetreten: \n\n"
                    + ex.getMessage(),
                    "Hinweis", 1);
        }
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new ChessApp(args).setVisible(true);
            }
        });
    }
In ChessApp wird auch die GUI erstellt und verwaltet. Sie dient ferner als Schnittstelle für die meisten Klassen. Die GUI selbst verfügt neben den Menü-, Symbol- und Statusleisten über die beiden Panele für das Schachbrett und die Info-Anzeige.

Die Klasse InOut ist für alle Ein- und Ausgaben im Bereich des Schachbrettes zuständig. Die Ausgabe umfasst einerseits die Darstellung des Schachbrettes mit seinen Figuren. Die Eingabe erfolgt ausschließlich über die Maustaste und bezieht sich ausschließlich auf das Ziehen der Figuren. Dafür wird bisher noch erst ein Startfeld mit einer Figur markiert und dann das Zielfeld. Später soll eine Figur auch erfasst und auf dem Zielfeld abgesetzt werden können.

Führt der Anwender einen Zug aus, wird dieser über den Aufruf von MoveCheck() geprüft, ob er ausführbar ist. MoveCheck() aus der Klasse Talk greift dafür auf die Klasse MovGen zurück. Falls der Zug nicht ausführbar ist, muss die Markierung des Startfeldes entfernt werden. Andernfalls wird die Figur aus dem Startfeld zu entfernt und auf das Zielfeld gesetzt. Dies geschieht von der Methode enterMove() der Klasse Talk, welche von MoveCheck() nach erfolgreicher Prüfung abschließend aufgerufen wird. enterMove() habe ich in meinem ersten Beitrag bereits gezeigt.

Das Eigenartige für mich ist nun, dass in enterMove() einmal die Zugbeschreibung und die Farbe des nächst Ziehenden ausgegeben sowie durch den Aufruf von UpdateBorad() der Klasse Board() über die Methode PrintPiece() der Klasse InOut die Figur auf ihre neue Position gesetzt, aber nur letzteres auch sofort angezeigt wird.

Bis hierher gäbe es noch kein Problem, aber nun, wenn der Zug ausführbar ist, muss der Gegenzug gesucht werden und das kostet Zeit, eventuell je nach Leveleinstellung sogar sehr viel Zeit, in der über die GUI nichts angezeigt wird, weder der bereits getätigte Zug, noch die Informationen zur Zugsuche, also die laufende "Denkzeit", die Tiefe der Suche und die bisher beste gefundene Zugfolge.

Wie kann man nach dem Aufruf einer Methode die aufrufende Methode beenden, ohne ein Ergebnis abzuwarten? Wenn mir das gelingen würde, könnte ich ja von MoveCheck() aus alle in InOut erforderlichen Aufräumarbeiten vornehmen.
 
mihe7

mihe7

Andererseits ist es nahezu unmöglich, hier den gesamten Code zu zeigen, der im Zusammenhang mit meinem Problem ausgeführt wird.
Das Problem ist, dass man nicht sieht, was in welchem Thread ausgeführt wird.

Bis hierher gäbe es noch kein Problem, aber nun, wenn der Zug ausführbar ist, muss der Gegenzug gesucht werden und das kostet Zeit, eventuell je nach Leveleinstellung sogar sehr viel Zeit, in der über die GUI nichts angezeigt wird, weder der bereits getätigte Zug, noch die Informationen zur Zugsuche, also die laufende "Denkzeit", die Tiefe der Suche und die bisher beste gefundene Zugfolge.
Mal ausgehend davon, dass das alles im EDT ausgeführt wird (z. B. als Reaktion auf einen Mausklick), wäre die Suche nach dem Gegenzug in einem separaten Thread auszuführen. Die anzuzeigenden Informationen, die von dieser Suche abhängen, müssten dann am Ende des Threads aktualisiert werden, dazu wird das invokeLater verwendet.

Mal ein Beispiel:
Java:
import java.awt.BorderLayout;
import javax.swing.*;

public class Test {
    JLabel status = new JLabel(" ");
    JLabel message = new JLabel(" ");
    JButton button = new JButton("Klick mich");

    private void reset() {
        status.setText("Döse vor mich hin");
        message.setText(" ");
        button.setEnabled(true);
    }

    public void run() {
        reset();

        button.addActionListener(e -> machWas());

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(status, BorderLayout.SOUTH);

        frame.add(button, BorderLayout.NORTH);
        frame.add(message);
        frame.setSize(800, 600);
        frame.setVisible(true);
    }

    private void machWas() {
        // wird im EDT ausgeführt
        button.setEnabled(false);
        status.setText("Arbeite...");
        Thread th = new Thread(() -> schwereArbeit());

        th.start();
        // Methode wird sofort beendet
    }

    private void schwereArbeit() {
        // schwere Arbeit im separaten Thread
        try {
            for (int i = 0, n = 10; i < n; i++) {
                Thread.sleep(200);

                // UI-Update im EDT
                int step = i+1;
                SwingUtilities.invokeLater(() -> {
                    message.setText(String.format("Schritt %d von %d",
                           step, n));
                });
            }
        } catch (InterruptedException ex) {
            // ignore
        } finally {
            // in jeden Fall UI wieder zurücksetzen
            SwingUtilities.invokeLater(() -> {
                reset();
            });
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new Test().run());
    }
}
 
kodela

kodela

Danke für das Beispiel!

Ich habe es mir bereits genauer angesehen. Mal sehen, was und wie ich daraus etwas für mein Projekt nutzen kann. Fest steht, dass ich mein ganzes Konzept in Frage stellen muss, denn während der Suche erfolgt nicht nur keine Ausgabe für die GUI, auch die Timerklasse, über die ja bestimmt wird, wann die zulässige Suchzeit beendet ist, arbeitet erst wieder, wenn die in InOut angestoßene Suche beendet ist. Während der Zugsuche für das Programm habe ich also mindestens drei weitere Threads, die gleichzeitig aktiv sind, oder es sein sollten, die GUI, den MouseAdapter und den Timer.

Mal sehen, was mir da einfällt.
 
Thema: 

Verspätete Anzeige in Textfeldern

Passende Stellenanzeigen aus deiner Region:
Anzeige

Neue Themen

Anzeige

Anzeige
Oben