Text mit externem Editor bearbeiten

Hallo zusammen,

ich brauch mal ein bisschen Input und Diskussion.

Folgendes Problem:
Ich möchte aus meiner Anwendung herraus einen Text-Editor öffnen und dort soll Inhalt aus einer Textbox meiner Anwendung angezeigt werden. Wird der Editor geschlossen, soll der Text aus dem Editor in der Textbox der Anwendung angezeigt werden.
Der Anwender kann in den Optionen einen Editor wählen, der zur Bearbeitung genutzt werden soll, dazu gibt er nur den Namen der Exe-Datei an.

Vorraussetzungen:

  • Anwendung ist ein Eclipse-RPC
  • Betriebssystem ist Windows
  • Editor liegt nicht immer im gleichen Pfad

Meine Überlegungen:

  • Programm aufrufen geht mit Runtime.getRuntime().exec("notepad.exe datei.txt");
  • Text aus der Textbox in temporäre Datei zwischenspeichern und diese Datei später wieder auslesen
  • Aufruf wird formuliert wie bei “Ausführen” über das Startmenü

image

Meine Fragen:

  1. Geht dieser Aufruf auch mit bspw. Notepad++ oder muss ich dafür den kompletten Pfad kennen? Genauer gefragt, klappt dieser kurze Aufruf mit sämtlichen installieren Programmen? (portable wird nicht gehen)
  2. Wie bekomme ich mit, dass der Editor geschlossen wurde?
  3. Kann man dieses Vorhaben eventuell auch anders umsetzen?
  4. Gibt es vielleicht Probleme/Fallstricke, die ich gleich berücksichtigen sollte?

Ich wollte heut Nachmittag dann mal ein PoC anfangen.

Moin,

zu 1) vermutlich JA. Wenn das Programm über CMD aufrufbar ist, sollte das IMHO klappen. Ob für ALLE installierten Programme? Nun ja, kommt wohl drauf an, ob ein Programm eine Textdatei öffnen kann …

zu 2) Du könntest in einem Intervall die aktiven Prozesse nach dem Editor durchsuchen. Eine direkte Reaktion wird kaum möglich sein, denn sonst müsste sich der Editor ja bei Dir melden …
Ich habe sowas mal gebastelt, um einen speziellen Task zu killen:

static abstract class AbstractNativeProcessListingStrategy
	implements IProcessListingStrategy
{
	@Override
	public void listProcesses() 
		throws Exception
	{
		Process process = makeProcessListingProcessBuilder().start();
		Scanner scanner = new Scanner( process.getInputStream() );
		while( scanner.hasNextLine() )
		{
			String sTemp = scanner.nextLine();
			if( sTemp.contains("ConfigGeoDevice") )
			{
				String sPID = sTemp.substring( 30, 34 );
				String sCMD = "taskkill /PID " + sPID + " /F";   // "taskkill /PID nnnn /F"
				try
				{
					System.out.println( "===> (GeoDaten-Anzeige) " +  sCMD );
					Process proc = Runtime.getRuntime().exec( sCMD );
					proc.waitFor();
				}	
				catch( InterruptedException eIE )
				{
					eIE.printStackTrace();
				}
				catch( IOException eIO )
				{
					eIO.printStackTrace();
				}
			}
		}
		scanner.close();
		process.waitFor();
	}

	protected abstract ProcessBuilder makeProcessListingProcessBuilder();
} // class AbstractNativeProcessListingStrategy

Hoffe, das hilft Dir erstmal weiter :slight_smile:
‚anders‘/besser’ geht vermutlich immer :star_struck:

VG Klaus

Mit Desktop geht das Öffnen von System-Programmen plattformunabhängig:
http://wiki.byte-welt.net/wiki/Dokument_mit_Standardanwendung_öffnen_(Java)

@vfl_freak
Wenn ich die aktiven Prozesse durchsuche, woher weiß ich dann das der gefundene Prozess meiner ist und nicht einer der unabhängig von meiner Anwendung gestaret wurde?

Aber ich sehe gerade dass du in deinem try-Block ein proc.waitFor() drin hast. Laut API wartet meine Anwendung drauf dass der gestartet Prozess beendet wird. Genau das, was ich suche :sunglasses:

@L-ectron-X
Das aus dem Wiki sieht sehr simpel aus, allerdings muss der vom Anwender gewünschte Editor nicht der gleiche Editor sein, der als Standard-Programm für *.txt festgelegt wurde. Ich werd aber mal nachfragen, ob das eventuell ausreichend ist.

Auf das Wiki bin ich generell nicht gekommen, hatte nur geschaut ob es hier im Forum schon was dazu gibt.

Zu 2) das mit dem waitFor() dürfte soweit funktionieren, dass dies solange wartet solange der Editor geöffnet ist.

Zu 4) Angenommen du hast einen Editor mit “Tabs”, der bereits geöffnet ist. Dann wirst du damit wohl einen neuen Tab aufmachen. Jetzt geht der user aber hin und schließt nur den einen Tab und das waitFor läuft ins ewige.

Je nach Usecase kann man auch hingehen einen Modalen Dialog öffnen mit Button(“Editing done”), den externen Editor starten und dann darauf warten bis der User auf den Button klickt.

Wenn er allerdings erst auf den Button klickt, dann erst speichert läuft das irgendwie ins leere…

Aber wie macht das eigentlich Eclipse selbst? Dort gibt es doch auch die Möglichkeit über das Kontextmenü Dateien mit einem Systemeditor zu öffnen?

Was mir noch einfällt ist auf die Temporäre Datei einen Watchservice zu legen.

https://docs.oracle.com/javase/tutorial/essential/io/notification.html

Moin,

ok, das würdest Du wohl im Zweifel gar nicht unterscheiden, es sei denn Du könnst Dir bei Auferuf die PID ermitteln und merken. Dann könntest du das „taskkill“ aus meinem Beispiel oben ja direkt nutzen.
Das ist aber sicher trivial …

Ich habe ein ähnliches Problem, wenn auf einen Rechner mehrere Java-Programme gleichzeitig laufen. Dann lassen sich die einzelnen „jplauncher“-Prozesse auch nicht unterscheiden!

VG Klaus

Okay, genau dieses Problem hab ich mit Notepad++. Ist Notepad++ mit einigen Dateien geöffnet und ich öffne dann meine Datei über Java, wartet waitFor() bis irgendeine Datei geschlossen wurde. Vielleicht gibt es aber eine Möglichkeit eine neue Programminstanz zu erzeugen.

Beim Windows-Standard-Editor kann mir das nicht passieren. Hier gibt es je Datei einen Prozess.


Edit: Notepad++ kann man mit „-multiInst“ als neue Instanz öffnen und der Windows-Editor ignoriert das ohne Fehlermeldung :sunglasses:

waitFor() wartet bis der Editor geschlossen wurde.

Windows-Standard-Editor macht aber auch so wenig Sinn, weil diese Funktionalität ja locker mittels einer Textbox abgedeckt werden kann.

Wie bereits erwähnt einen Watchservice nutzen, der dann bei Änderungen benachrichtigt wird und dann im Javaprogramm die Änderungen aktualisiert.

Wenn es nur um Windows ginge, dort kann man sich die jeweiligen File Handles holen und schauen, ob eine bestimmte Datei noch von einem Prozess gehalten wird.

https://msdn.microsoft.com/en-us/library/windows/desktop/aa364225(v=vs.85).aspx

Die Frage ist hier nur, ob man sich mit Temporären Dateien hier nicht selbst ins Bein schiesst, da diese ja immer noch von Java gehalten werden um beim Beenden des Programms eliminiert zu werden.

Es geht darum, dass man viel Text besser in einem Editor bearbeiten kann als in einer Textbox. Der Test enthält auch keine Textstyles, daher wäre grundsätzlich der Windows-Editor ausreichend.

Ich hab mir das aber auch nicht ausgedacht. Der Kunde hat so eine Lösung aktuell mit Python auf Linux umgesetzt und dort wird NEdit als Standard-Editor genommen, ich habe allerdings keine Infos welche Editoren unter Linux dafür noch zum Einsatz kommen.

Ich hab das Ganze jetzt wie folgt gelöst:

package de.test.externereditor.parts;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import javax.annotation.PostConstruct;
import org.eclipse.e4.ui.di.Focus;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Button;

public class SamplePart {

    private Text text;
    private Button btnEditor;

    @PostConstruct
    public void createComposite(Composite parent) {
        parent.setLayout(new GridLayout(2, false));
        
        text = new Text(parent, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.CANCEL | SWT.MULTI);
        text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 2));
        new Label(parent, SWT.NONE);
        
        btnEditor = new Button(parent, SWT.NONE);
        btnEditor.setText("Editor");
        btnEditor.addListener(SWT.Selection, new Listener(){

            @Override
            public void handleEvent(Event event) {
                try {
                    String filePath = writeTempFile();
                    StringBuilder cmd = new StringBuilder();
                    cmd.append("notepad "); // Editor
                    cmd.append(filePath); // temp file
                    Process proc = Runtime.getRuntime().exec(cmd.toString());
                    proc.waitFor();
                    readTempFile(filePath);
                } catch (IOException | InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });
    }

    @Focus
    public void setFocus() {
        btnEditor.setFocus();
    }
    
    private String writeTempFile(){
        String fileName ="";
        try {
            File tempFile = File.createTempFile("editor", ".txt");
            fileName = tempFile.getAbsolutePath();
            FileWriter tmpWriter = new FileWriter(tempFile);
            tmpWriter.write(text.getText());
            tmpWriter.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return fileName;
    }
    
    private void readTempFile(String filePath){
        try {
            File tempFile = new File(filePath);
            FileReader tmpReader = new FileReader(tempFile);
            BufferedReader buffer = new BufferedReader(tmpReader);
            StringBuilder builder = new StringBuilder();
            String line;
            while((line=buffer.readLine()) !=null){
                builder.append(line);
            }
            text.setText(builder.toString());
            tempFile.delete(); // delete temp file
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

Anregungen sind auch weiterhin noch gern gesehen.

Ok, zwei Anregungen

Man kann der Runtime bei exec auch ein String[] übergeben. Da braucht man den String nicht extra zusammenzufriemeln. Leerzeichen in Pfaden werden damit auch gleich miterledigt, wenn ich mich nicht ganz täusche.

Beim Einlesen, werden die Zeilenumbrüche weggeworfen. Aber hey, vielleicht ist das ja auch absicht. :sunglasses:

1 „Gefällt mir“

Ähm nein, keine Absicht sondern ein Fehler meinerseits :smiley: Danke für den Hinweis.

Das mit dem String schau ich mir nochmal genauer an, danke auch für diesen Hinweis.

Es gibt nur 3 Möglichkeiten:

  • Über die Klasse Desktop, das ist der Königsweg,
  • über .exec(), wie von dir oben beschrieben,
  • einen ‘eigenen’ Texteditor bereitstellen…

Alles Vor- und Nachteile, aber ich würd zu was Eigenem tendieren.

Wenn es sehr viel bla bla blubb ist, DANN hat auch mancher Texteditor damit Schwierigkeiten, etwa ab 1048576 Zeichen…

Das mit dem vielen Text wird nicht passieren. Das DB-Feld wo der Inhalt später landet ist auch begrenzt und der kleine Kreis der Anwender weiß darum.

Hast du das lösen können? Ich hatte nämlich das gleiche Problem, Editor ist (i.d.R) Notepad++, wie bekomme ich das Schließen des Dokuments mit (andere Dokumente können noch in Reitern geöffnet sein)?

Ich hab in meiner Anwendung (die erstmal nur ich bediene) einen Knopf zum neu einlesen eingebaut, aber eine automatische Erkennung wäre natürlich echt vorzuziehen.

Ich hab mich gegen die Lösung des eigenen Editors entscheiden, weil die Benutzer (erstmal ich selbst) die ganzen komfortablen Möglichkeiten eines echten Editors haben sollen. Und das nachzuprogrammieren wäre ja eine ziemliche Zeitverschwendung und Aufwand.

Könnte man hier nicht einfach jedes mal das Dokument lesen, wenn dein Programm wieder den Focus bekommt?
Es ist ja davon auszugehen, dass wenn dein Programm den Focus hat, dass man mit ihm (erst mal) wieder arbeiten möchte.

Also wenn es auf Basis von Eclipse RCP sein soll, dann gibt es den Editor fast schon geschenkt.

http://www.vogella.com/tutorials/EclipseEditors/article.html#texteditors-and-sourceviewer

Das ist wirklich überschaubar. @Crian hast du mal überlegungen bzgl. des Watchservices angestrebt

Bei notepad++ starte ich mit dem zusätzlichem Argument „-multiInst“ eine neue Instanz. Wenn der Anwender dort dann zwischenzeitlich weitere Dateien geöffnet hat, hat man natürlich wieder verloren.

Ah interessant, so kann man eine neue Instanz starten, auch wenn eine offen ist und in den Einstellungen ausgewählt wurde, dass es nicht mehrere gibt?

Allerdings soll das Programm eigentlich bei beliebigem Editor (manche hier nutzen andere) funktionieren. Trotzdem werde ich das mal im Hinterkopf behalten.

Schlaue Idee! Die sollte eigentlich funktionieren, es sei denn, der Benutzer „vertappt“ sich, weil irgendwas anderes die Arbeit stört (E-Mail, Telefonanruf, bei dem was zu tun ist) und landet verfrüht in meiner Anwendung. Trotzdem ist das eine echt gute Idee.

Nein, da ich es bislang nicht kannte (was nun eine Googlesuche nach „java watchservice“ geändert hat).

Dieses Forum ist immer wieder eine gute Anlaufstelle. Da forsche ich nun mal etwas herum!

Mal schauen ob es bei dem Watchservice ein Problem macht, wenn man das Dokument auch zwischendurch schon mal speichert, und nicht nur einmal am Ende.

Nunja “Problem”. Du wirst informiert dass die Datei geändert wurde. Aber wie soll das Betriebssystem entscheiden, dass der Benutzer “fertig” ist oder nicht?

Btw hatte ich mal ein Projekt gemacht welches unter anderem Watchservices kapselt. Leider weiß ich selber dass es nicht unbedingt soo stabil sein könnte bzw. habe es nicht näher getestet und die “installation” ist auch etwas hakelig, da nicht Maven Central und mehrere Maven Projekte