Swing Programm hängt sich bei Buttondruck auf? (GUI für "Chatbot" erstellen)

PinkMuffin

Bekanntes Mitglied
Hallo,
ich mach im Moment Studienaufgaben für das nächste Semester und eine der Aufgaben ist es, einen Chatbot zuerst zu schreiben und dann hinterher ein Gui dazu zu erstellen. Ich nehme mal an, dass sie uns dadurch gleich dazu kriegen wollen, das sinnvoll zu trennen, allerdings habe ich dabei noch ein paar Schwierigkeiten.
Im Moment hängt sich jedes mal mein Programm auf, sobald ich auf den Button klicke, das Fenster lässt sich dann auch nicht mehr schließen.

[CODE lang="java" title="Gui" highlight="11-20"] public Gui(Chatbot bot)
{
this.bot = bot;
frame = new JFrame("Example");
frame.setBounds(0, 0, 600, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
textArea = new JTextArea();
textArea.setEditable(false);
textField = new JTextField(48);
button = new JButton("senden");
button.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent arg0)
{
appendText(textField.getText());
bot.eingabe = textField.getText();
textField.setText(null);
bot.auswahl();
}
}) ;
southPanel = new JPanel();
southPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
southPanel.add(textField);
southPanel.add(button);
frame.getContentPane().add(textArea, BorderLayout.CENTER);
frame.getContentPane().add(southPanel, BorderLayout.SOUTH);
}[/CODE]

Java:
public class Chatbot
{
    public static String eingabe;
    public static String benutzer = "Benutzer";
        
    //Methode "starten" entfernt, da hier alles funktioniert
    
    public void auswahl()
    {
        boolean weiter = true;
        do
        {
            
            System.out.println("Botti:"+"\t"+"\t"+"Wie kann ich dir helfen? (gebe "+ "\"help\""+ " ein, um mögliche Funktionalitäten anzuzeigen.)");
            System.out.print(benutzer +":");
            
            switch(eingabe)
            {
            case "help":
                help();
                break;
            case "ssp":
                spieleSSP();
                break;
            case "rechnen":
                berechneSumme();
                break;
            case "der wievielte ist heute?":
                sageDatum();
                break;
            case "wie viel uhr ist?":
                sageUhrzeit();
                break;
            case "stop":
                weiter = false;
                System.out.println("Botti:"+"\t"+"\t"+"Ich wünsche dir noch einen schönen Resttag, " + benutzer+".");
                break;
            default:
                System.out.println("Botti:"+"\t"+"\t"+"Die Eingabe ist ungültig, bitte versuche es erneut.");
            
            }
            
            
        }while (weiter);
            
    }

Der ActionListener von button führt keine einzige seiner Aktionen aus, wobei appendText und textField.setText(null); funktioniert haben, bevor ich versucht habe, die Eingabe über das TextField zu befüllen.
 
Im Moment hängt sich jedes mal mein Programm auf, sobald ich auf den Button klicke
Das Programm hängt so lange, wie die Methode "actionPerformed" läuft (auch die Oberfläche wird dann nicht neu gemalt).
Da du in "actionPerformed" die Methode "auswahl" aufrufst, die dann wiederum weitere Methoden aufruft, wird wahrscheinlich "actionPerformed" nie beendet.
 
Ok, logisch. Mit einem repaint innerhalb von "actionPerformed" ging es allerdings auch nicht, und auch das Fenster lässt sich nicht schließen, müsste das nicht unabhängig dazu laufen? Weil ich kann es sonst ja auch an jedem beliebigen Punkt schließen.
Da du in "actionPerformed" die Methode "auswahl" aufrufst, die dann wiederum weitere Methoden aufruft, wird wahrscheinlich "actionPerformed" nie beendet.
Ich frage mich halt, wie ich es dann lösen sollte. Meine auswahl Methode ist auf die Eingabe im JTextField angewiesen..
 
Wenn die GUI blockiert ist, dann ist diese blockiert. Da kann diese auf nichts mehr reagieren. Das Fenster schließen ist ja auch nur ein Event in der Event-Queue, das besagt: Fenster soll geschlossen werden. Und so lange diese Event nicht bearbeitet wird, passiert nichts!

Generell: Von der GUI aus machst Du nichts, das irgendwie länger dauern könnte oder gar blockiert.

Dieses Auswahl ist aber doch eine blockierende Abfrage auf der Konsole - das hat doch in einer GUI nichts verloren! Also ganz klar ein Design-Fehler.
 
Dieses Auswahl ist aber doch eine blockierende Abfrage auf der Konsole - das hat doch in einer GUI nichts verloren! Also ganz klar ein Design-Fehler.
Ja, soweit hat es fhoffmann ja auch erklärt, ich hab ja selbst gesagt, dass ich nicht genau weiß, wie man das mit der Kommunikation zwischen dem GUI und dem eigentlichem Programm besser lösen kann, daher frage ich ja. Vom Programm zum GUI hab ich dazu einen guten Beitrag gefunden, die andere Richtung ist allerdings (wie man sieht) etwas problematisch.
 
Alles, was bisher konsole I/O war, kommt komplett in den Müll.

Und dann überlegst Du Dir erst einmal:
- Wie soll die Oberfläche aussehen?
- Was soll passieren, wenn der Anwender bestimmte Aktionen ausführt?

Wenn es eine Texteingabe geben soll, dann kannst Du dazu ein Textfeld nutzen. Aber die Auswertung erfolgt dann über Button-Click oder Return oder .... (Musst Du festlegen).

Und dann reagiert du und machst, was getan werden soll (so es kurz und schnell ist), also z.B. bei "Datum" gibst Du irgendwo das Datum aus.
 
Danke, ich glaube das hat mir einiges weitergeholfen. Ich hab es jetzt so geändert, dass der bot ein gui kennt, bei dem er in die TextArea schreiben kann, soweit war es ja erstmal unkompliziert.
Java:
    public synchronized void chatbotStarten() throws InterruptedException
    {
        Scanner scan = new Scanner(System.in);
        
        // Systemuhrzeit wird ausgelesen und eine entsprechende Begrüßung ausgegeben
        SimpleDateFormat df = new SimpleDateFormat("HH");
        String time = df.format(new Date());
        int zeit = Integer.parseInt(time);
        if(zeit<=11 && zeit>=0 )
        {
            gui.appendText("Botti:"+"\t"+"\t"+"Guten Morgen");
        }
        else if(zeit >=12 && zeit<=17)
        {
            gui.appendText("Botti:"+"\t"+"\t"+"Guten Tag");
        }
        else if(zeit>=18 && zeit >=23)
        {
            gui.appendText("Botti:"+"\t"+"\t"+"Guten Abend");
        }
        else
        {
            gui.appendText("Botti:"+"\t"+"\t"+"Hallo");
        }
        gui.appendText("Botti:"+"\t"+"\t"+"Ich bin Botti, wie ist dein Name?");
        gui.appendText(benutzer +":"+ "\t");       
    }

Die "auswahl" ist jetzt Aufgabe des Gui,
Java:
            button.addActionListener(new ActionListener()
                    {

                        @Override
                        public void actionPerformed(ActionEvent arg0)
                        {
                            String eingabe = textField.getText();
                            appendText(textField.getText());
                            if(buttonCount==0)
                            {
                                bot.begruessung(textField.getText());
                            }
                            else
                            {
                                    switch(eingabe)
                                    {
                                        case "help":
                                            bot.help();
                                            break;
                                        case "ssp":
                                            bot.spieleSSP();
                                            break;
                                        case "rechnen":
                                            bot.berechneSumme();
                                            break;
                                        case "der wievielte ist heute?":
                                            bot.sageDatum();
                                            break;
                                        case "wie viel uhr ist?":
                                            bot.sageUhrzeit();
                                            break;
                                        case "stop":
                                            System.out.println("Botti:"+"\t"+"\t"+"Ich wünsche dir noch einen schönen Resttag, " + bot.benutzer+".");
                                            break;
                                        default:
                                            System.out.println("Botti:"+"\t"+"\t"+"Die Eingabe ist ungültig, bitte versuche es erneut.");           
                                    }
                            }   
                            textField.setText(null);
                            buttonCount++;
                        }
                        
                
                    });

Allerdings entsteht jetzt immer dann ein Problem, wenn innerhalb einer Funktion erneut eine Eingabe (also ein Abruf von textField) nötig wäre..
Dadurch, dass der erste Klick nicht abgeschlossen werden kann (weil die Funktion darin ja noch läuft) und für den Abschluss ein weiterer Klick notwendig wäre, wegen der Eingabe, weiß ich nicht ganz, was ich machen kann. Ich möchte es eigentlich bei diesem einen Button belassen.
 
Du musst Dir überlegen, wie du diese Eingabe machen möchtest. Es ist immer das Gleiche: Du passt nur die UI an um dann deine Änderungen abzuschließen.

Was bei weiteren Abfragen z.B. verwendet werden kann ist ein (ggf. modaler) Dialog.

Schau Dir doch einfach mal aktiv diverse Oberflächen an. Was passiert denn da? Wie bedient man ein Programm? Mach es Dir bewusst! Also einfacher Editor und Du speicherst ... Dann kommt ein Fenster, das dich nach Dateiname und Ort fragt...
 
Aber genau hier hört doch mein Verständnis auf, da hilft dann eben kein "Mach es Dir bewusst" mehr.
Letzteres (Datei abspeichern) habe ich bereits einmal programmiert. Ich habe auch schon eine Authentifizierung vor einem Programm programmiert (also nach Benutzername und Passwort gefragt), aber der Unterschied ist, dass ich da immer neue Frames geöffnet und dann nach der Eingabe discarded habe (ein Button war also immer für genau eine Eingabe zuständig), gerade das möchte ich ja vermeiden.
Soweit ich das verstehe, würde bei einem Dialog das gleiche in grün passieren (durch das Buttonevent würde ein Dialog ausgelöst, der wiederum nach der weiteren Eingabe fragt, beliebig tief verschachtelt), oder verstehe ich das falsch?
 
Irgendwo musst Du Ausgabe und Eingabe plazieren. Wenn es kein neues Fenster sein soll, dann muss es im aktuellen Fenster sein.

Findet sich doch auch regelmäßig. Die IDE Deiner Wahl wird dies vermutlich ständig machen. Da wird dann einfach ein Bereich für Ausgabe/Eingabe verwendet...

Hier ist halt wichtig: Wie soll sich die Applikation verhalten - das kannst nur Du vorgeben. Du kannst Dazu z.B. Skizzen auf Papier machen. Wie soll das Fenster aussehen? Und wenn dann auf einen Knopf gedrückt wird oder irgend was anderes passiert: Wie soll es dann aussehen? Dieses Verhalten musst Du festlegen. Das können wir dir auch nicht sagen, denn wir wissen nicht, was für Anforderungen du umsetzen musst.

Wenn Du es wie eine Console 1:1 umsetzen willst, dann kannst Du z.B. genau das abbilden:
- Du hast einen Textbereich, der die Ausgabe zeigt.
- Eine Eingabe-Box ist da.

Und dann kannst Du da alle Abfragen anzeigen:
Dann fügst Du bei der Ausgabe hinzu (oder ersetzt diese) durch eine Frage:....
"Wie alt bist du?"
Und dann kann man da im Eingabebereich etwas eingeben.
Du hast dann halt bei der Auswertung der Eingabe eine Art status, der Dir sagt: Was soll dies sein? Also weisst Du, dass gerade ein Alter erwartet wird. Wenn da nun keine Zahl steht, dann kannst Du meckern: "Das war keine Zahl". Oder wenn da Unsinn steht a.la. 1000 oder -17, dann kannst Du sagen: "So alt kann man nicht sein!"
 
Irgendwo musst Du Ausgabe und Eingabe plazieren. Wenn es kein neues Fenster sein soll, dann muss es im aktuellen Fenster sein.

Findet sich doch auch regelmäßig. Die IDE Deiner Wahl wird dies vermutlich ständig machen. Da wird dann einfach ein Bereich für Ausgabe/Eingabe verwendet...

Hier ist halt wichtig: Wie soll sich die Applikation verhalten - das kannst nur Du vorgeben. Du kannst Dazu z.B. Skizzen auf Papier machen. Wie soll das Fenster aussehen? Und wenn dann auf einen Knopf gedrückt wird oder irgend was anderes passiert: Wie soll es dann aussehen? Dieses Verhalten musst Du festlegen. Das können wir dir auch nicht sagen, denn wir wissen nicht, was für Anforderungen du umsetzen musst.
Wie gesagt, dass habe ich doch alles bereits festgelegt und komme an der Stelle eben nicht weiter:
Ich habe eine Ausgabe in der TextArea, die funktioniert auch soweit.
Ich habe eine Eingabe durch ein JTextField, das funktioniert eben nur mehr oder weniger.
Ein Button soll die Eingabe aus dem TextField an den Chatbot übergeben und in die TextArea schreiben und es muss mehrfach eine Eingabe möglich sein und an dieser Stelle funktioniert es nicht mehr.
Der Chatbot wartet nicht auf die Eingabe vom Button, sondern zieht nach einmaliger Eingabe sein ganzes Programm durch, wodurch logischerweise Fehler auftreten.
Also hab ich gedacht, die Methoden werden einfach nur noch vom Button ausgelöst, dann muss der Bot ja "warten", aber dadurch entsteht obiges Problem, dass der Button im gedrückten Zustand bleibt und eine neue Eingabe nicht möglich ist. Eigentlich will ich nur, dass der Bot an jeder Eingabestelle wartet, bis der Button eine neue Eingabe übergibt.
 
Du hast den Chatbot somit nicht wirklich von der bisherigen UI (=Konsole) getrennt.

Da musst Du den ChatBot anpassen, damit der eben nichts ausgibt und keine Eingabe einfordert. Das darf nicht sein. Alle Konsolen-Aus/Eingabe muss komplett verschwinden.
 
Irgendwie bin ich aus den Beiträgen nur so mäßig schlau geworden, vielleicht ist meine Frage einfach unklar oder ich stehe besonders ausdauernd auf dem Schlauch..

Ich habe jetzt das UI und den Chatbot jetzt vollständig voneinander getrennt, ich hab mal gelesen, dass man das mit einem Controller machen kann, aber meine Lösung fühlt sich einfach grundlegend unpraktisch an, deshalb kann ich irgendwie nicht glauben, dass es da nicht ein BestPractise gibt..
Besonders bei den Mehrfacheingaben habe ich einen sehr uneleganten workaround gemacht, indem jetzt der Button überprüft, wie die vorherige Eingabe war und ob aufgrund der vorherigen Eingabe jetzt zwangsweise eine andere Abfrage folgen muss.

[CODE lang="java" title="Der Chatbot (nur die Funktionen für SchereSteinPapier, da hier das Problem liegt)"] public String spieleSsp2(String eingabe)
{
Random zufall = new Random(); //nextInt gibt Zahl zwischen 0 und ... aus, daher "+1"
int computerWahl = zufall.nextInt(3)+1;
int spielerWahl = Integer.parseInt(eingabe);
String returnString="";
switch(spielerWahl)
{
case 1:
switch(computerWahl)
{
case 1:
returnString=("Botti:"+"\t"+"\t"+"Oh, ich habe auch die Schere. Das heißt wohl unentschieden"+"\n");
break;
case 2:
returnString=("Botti:"+"\t"+"\t"+"Ha! Ich habe den Stein. Du hast verloren."+"\n");
break;
case 3:
returnString=("Botti:"+"\t"+"\t"+"Sowas Doofes. Ich hab das Papier. Da hast du wohl gewonnen."+"\n");
break;
}
break;
case 2:
switch(computerWahl)
{
case 1:
returnString=("Botti:"+"\t"+"\t"+"Sowas Doofes. Ich hab die Schere. Da hast du wohl gewonnen."+"\n");
break;
case 2:
returnString=("Botti:"+"\t"+"\t"+"Oh, ich habe auch den Stein. Das heißt wohl unentschieden"+"\n");
break;
case 3:
returnString=("Botti:"+"\t"+"\t"+"Ha! Ich habe das Papier. Du hast verloren."+"\n");
break;
}
break;
case 3:
switch(computerWahl)
{
case 1:
returnString=("Botti:"+"\t"+"\t"+"Ha! Ich habe die Schere. Du hast verloren."+"\n");
break;
case 2:
returnString=("Botti:"+"\t"+"\t"+"Sowas Doofes. Ich hab den Stein. Da hast du wohl gewonnen."+"\n");
break;
case 3:
returnString=("Botti:"+"\t"+"\t"+"Oh, ich habe auch das Papier. Das heißt wohl unentschieden"+"\n");
break;
}
break;
case 42:
returnString=("Botti:"+"\t"+"\t"+"Du hast die Antwort auf den Sinn des Lebens gefunden. Nun müsstest du nur noch die Frage wissen.."+"\n");
break;
default:
returnString=("Botti:"+"\t"+"\t"+"Wenn du falsche Zahlen wählst, können wir nicht spielen, Schummler verlieren immer."+"\n");
break;
}

return returnString;
}
[/CODE]

Der Bot kennt kein UI und umgekehrt, dafür hab ich den Zwischenschritt Controller eingeführt

[CODE lang="java" title="Controller (ebenfalls nur der SSP-Ausschnitt)"]public class Controller
{
private Chatbot bot;
private UI ui;
public String vorher="";
public Controller(Chatbot bot)
{
this.bot = bot;
ui = new UI(this);
ui.appendText(bot.chatbotStarten());
}

public void ssp1Starten()
{
ui.appendText(bot.spieleSSp1());
}

public void ssp2Starten()
{
ui.appendText(bot.spieleSsp2(eingabe));
}
[/CODE]

In der UI ist jetzt das Konstrukt, das mir auf die Nerven geht: die Variable "vorher" und der buttonCounter.
Der ButtonCounter ist benötigt, da man die Variable Benutzer ja ausschließlich beim ersten mal setzt. Das "vorher" ist dazu da, um SSP-Spiele oder berechnungen zu ermöglichen.
[CODE lang="java" title="Der ActionListener vom Button"] button.addActionListener(new ActionListener()
{

@Override
public void actionPerformed(ActionEvent arg0)
{

eingabe = textField.getText();
appendText(benutzer +"\t"+"\t"+eingabe);
ctg.eingabe = eingabe;
textField.setText(null);
if(buttonCounter==0)
{
benutzer = eingabe;
ctg.begruessungStarten(eingabe);
}
else if(ctg.vorher.equals("ssp"))
{
ctg.vorher="";
ctg.ssp2Starten();
}
else if(ctg.vorher.equalsIgnoreCase("rechnen"))
{
ctg.vorher="rechnen2";
ctg.berechneSumme2Starten();
}
else if(ctg.vorher.equalsIgnoreCase("rechnen2"))
{
ctg.vorher ="";
ctg.berechneSumme3Starten();
}
else
{
eingabe = eingabe.toLowerCase();
switch(eingabe)
{
case "help":
ctg.helpStarten();
break;
case "ssp":
ctg.vorher="ssp";
ctg.ssp1Starten();
break;
case "rechnen":
ctg.vorher = "rechnen";
ctg.berechneSumme1Starten();
break;
case "der wievielte ist heute?":
ctg.sageDatumStarten();
break;
case "wie viel uhr ist?":
ctg.sageUhrzeitStarten();
break;
case "stop":
appendText("Botti:"+"\t"+"\t"+"Ich wünsche dir noch einen schönen Resttag, " + "benutzer"+".");
frame.dispose();
break;
default:
appendText("Botti:"+"\t"+"\t"+"Die Eingabe ist ungültig, bitte versuche es erneut.");
}
}
buttonCounter++;
}
[/CODE]
Es funktioniert alles, daher danke für den Denkanstoß @kneitzel , aber irgendwie habe ich immer noch das Gefühl, dich nicht ganz richtig verstanden zu haben, oder würde man das im Allgemeinfall so lösen?
 
Also die generelle Idee ist durchaus richtig. Aber die Implementierung wird in der Regel etwas anders aufgebaut.

Was man in der Informatik gerne hat, ist sowas wie eine "State Machine": https://de.wikipedia.org/wiki/Endlicher_Automat

Und je nachdem, in was für einem Zustand du bist, wird halt etwas anderes gemacht.

Dabei würde ich noch sagen: Diese Darstellung als Automat ist einfach nur eine Formalisierung. Streng genommen hast Du etwas ähnliches bei jedem if. Du prüfst halt den Zustand um dann irgend etwas zu entscheiden... Man kann es also zur Darstellung von Algorithmen nutzen.

Und je nachdem, was man braucht, kann man dies aber auch prinzipiell so oder ähnlich auch in Klassen gießen. Dann hätte Dein Client einen Zustand und je nach Eingabe wechselt er den Zustand.

Grundzustand:
- "rechnen" --> Rechnerzustand
- ....
- "stop" --> ENDE

Beim Rechnerzustand hast Du dann halt wieder die Auswertung von Eingaben mit unterzuständen ... Du hast also einen START von dem aus du in (Eingabe erste Zahl) gehst. Durch Eingabe einer Zahl kommst Du zu (Eingabe Operand) und nach Eingabe des Operanden kommst du zu (Eingabe zweite Zahl). Sobald die eingegeben wurde, rechnest Du, gibst das Ergebnis aus und wechselst in den Grundzustand.
Nur um mal etwas grob zu skizzieren.

Wie man das genau modellieren könnte, könnte man sich überlegen....

Jeder Knoten hat eine Liste von Aktionen.
Diese Aktion hat ein Ergebnis (welches z.B. immer ein String sein könnte).
Jeder Knoten hat eine Map von Predicate<String> zu Folgeknoten.

Aktionen können diverse Elemente sein, die die GUI ausführen kann:
- Irgend etwas aufrufen - dann gibt es konkreten Code, der ausgeführt wird mit einem definierten Interface (So kann etwas mitgegeben werden um z.B. Dinge auszugeben)
- Eingabe - Wenn sowas gefordert wird, dann:
-> wird die Eingabe aktiviert (Also bei einer Oberfläche z.B. eine Inputbox eingeblendet, aktiviert, was weiss ich und das war es... erst bei einem bestimmten Event (Button, Return, ...) wird die Eingabe ausgewertet und die Abarbeitung geht weiter ..

Also es gibt viele Möglichkeiten - da kann ich auch nicht sagen, was richtig oder falsch ist. Generell ist es auch ok, einfach irgendwelche Variablen zu prüfen um dann bestimmte Dinge zu machen oder eben nicht zu machen.... Ich wollte nur grob Skizzieren, wie es aussehen könnte ...
 
Achso okay, danke.
Dann schau ich mir die State Machine mal spaßeshalber vor dem Theorieunterricht an, aber für eine kleine Anwendung wie mein Bot war das if vielleicht sogar unkomplizierter, ich war nur etwas irritiert, weil ein Lehrer mal gesagt hat, dass wenn man zu viele ifs für eine Sache verwendet, man die Sache meist falsch angeht.
 
Achso okay, danke.
Dann schau ich mir die State Machine mal spaßeshalber vor dem Theorieunterricht an, aber für eine kleine Anwendung wie mein Bot war das if vielleicht sogar unkomplizierter, ich war nur etwas irritiert, weil ein Lehrer mal gesagt hat, dass wenn man zu viele ifs für eine Sache verwendet, man die Sache meist falsch angeht.
Ja, aber das ist dann eher ein Zeichen, dass man es auch unterteilen und aufteilen sollte. Aber das ändert nichts an der eigentlichen Problematik
 

Zurück
Oben