SWT SWT flackern beheben

P@u1

Aktives Mitglied
Hallo zusammen,
ich hab mal ein bischen angefangen mit SWT (vorher habe ich nur mit Swing gearbeitet) und wollte nen bischen was zeichnen, möglichst ohne störendes flackern.
Ich hab schon ein Canvas mit SWT.DoubleBuffered benutzt, das ergebnis hat mich aber nicht wirklich überzeugt (flackern trat trotzdem auf).
Jetzt habe ich ein org.eclipse.swt.graphics.Image benutzt, es geht ganz gut, aber ein kleines bischen flackert es immer noch ab und zu...
Wie kann ich das besser machen?
Und außerdem finde ich es etwas umständlich bei SWT ein repaint anzuforder, ich habe das mit
Java:
display.asyncExec(new Runnable() {
						public void run() {
							if (!shell.isDisposed())
								shell.redraw();
						}
					});
gemacht, gibts vll bessere Alternativen dazu?

Dann hier drunter nochmal das was ich gemacht hab, bei mir flackert es ab und zu ein kleines bischen, nicht super schlimm, aber ich fänds besser, wenns ganz weg wäre.
Das Beispiel sollte eigentlich kompilierbar sein.

Java:
import java.util.Random;

import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class SWTThreadTest {
	public static void main(String[] args) {
		final Display display = Display.getDefault();
		final Shell shell = new Shell(display);
		int WIDTH = 1000;
		int HEIGHT = 1000;
		shell.setSize(new Point(WIDTH, HEIGHT));
		shell.open();
		final Image img = new Image(display, WIDTH, HEIGHT);
		final GC gc = new GC(img);
		shell.addPaintListener(new PaintListener() {
			@Override
			public void paintControl(PaintEvent e) {
				e.gc.drawImage(img, 0, 0);
			}
		});
		new Thread() {
			{
				setDaemon(true);
			}

			public void run() {
				Random rnd = new Random();
				int d = 1000;
				while (true) {
					gc.drawLine(rnd.nextInt(d), rnd.nextInt(d), rnd.nextInt(d),
							rnd.nextInt(d));
					display.asyncExec(new Runnable() {
						public void run() {
							if (!shell.isDisposed())
								shell.redraw();
						}
					});
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}.start();
		while (!shell.isDisposed())
			if (!display.readAndDispatch())
				display.sleep();
		display.dispose();
	}
}

Wenn jemand verbesserungsvorschläge hat -> bitte melden, danke 🙂
 
Der 2te Thread ist Grundfalsch. Du musst im UI Thread zeichnen. Dann brauchst du im Normalfall auch kein Image. Ausserdem fällt dann das asyncExec um das redraw weg.
Beispiel (so sieht es doch besser aus, oder 😉 ):

Java:
public class SWTThreadTest {
    public static void main(String[] args) {
        final Display display = Display.getDefault();
        final Shell shell = new Shell(display);
        int WIDTH = 1000;
        int HEIGHT = 1000;
        shell.setSize(new Point(WIDTH, HEIGHT));
        shell.open();
        shell.setLayout(new FillLayout());
        new MyCanvas(shell);
        while (!shell.isDisposed())
            if (!display.readAndDispatch())
                display.sleep();
        display.dispose();
    }
}

class MyCanvas extends Canvas implements PaintListener
{
	
    private Random rnd = new Random();

	public MyCanvas(Composite parent) {
		super(parent, SWT.NONE);
		addPaintListener(this);
		new Updater(this).schedule(1000);
	}

	@Override
	public void paintControl(PaintEvent e) {
		GC gc = e.gc;
		int d = 1000;
		for(int i=0;i<20;i++)
		{
			gc.drawLine(rnd.nextInt(d), rnd.nextInt(d), rnd.nextInt(d),
					rnd.nextInt(d));			
		}
		
	}
	
}

class Updater implements Runnable{
	private Canvas canvas;
	private int milliseconds;

	public Updater(Canvas canvas) {
		super();
		this.canvas = canvas;
	}
	
	public void schedule(int milliseconds){
		this.milliseconds = milliseconds;
		canvas.getDisplay().timerExec(milliseconds, this);
	}
	
	@Override
	public void run() {
		canvas.redraw();
		canvas.getDisplay().timerExec(milliseconds, this);
        
	}
}
 
Danke für deine Hilfe!

Es funktioniert bei mir undzwar ohne flackern (liegt das daran, dass canvas irgendnen buffering macht?).
Allerdings noch zwei Sachen: (die erste ist wesentlich wichtiger als die zweite):

- Die Linien kommen erst, sobald ich einmal die größe des fensters geändert habe - warum ist das so und wie kann mans ändern?
- So wird der ja alle x millisekunden neuzeichnen, auch wenn keine änderung vorliegt - einfach so akzeptieren, oder kann man das verbessern?

EDIT:
Bei deiner Lösung stimmt irgendwas noch nicht so ganz, ich habe gerade folgende Exception bekommen:
Java:
Exception in thread "main" org.eclipse.swt.SWTException: Widget is disposed
	at org.eclipse.swt.SWT.error(SWT.java:4083)
	at org.eclipse.swt.SWT.error(SWT.java:3998)
	at org.eclipse.swt.SWT.error(SWT.java:3969)
	at org.eclipse.swt.widgets.Widget.error(Widget.java:468)
	at org.eclipse.swt.widgets.Widget.checkWidget(Widget.java:340)
	at org.eclipse.swt.widgets.Control.redraw(Control.java:2175)
	at thredTest2.Updater.run(SWTThreadTest2.java:82)
	at org.eclipse.swt.widgets.Display.runTimer(Display.java:4167)
	at org.eclipse.swt.widgets.Display.messageProc(Display.java:3256)
	at org.eclipse.swt.internal.win32.OS.DispatchMessageW(Native Method)
	at org.eclipse.swt.internal.win32.OS.DispatchMessage(OS.java:2459)
	at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3655)
	at org.eclipse.swt.widgets.Display.release(Display.java:3713)
	at org.eclipse.swt.graphics.Device.dispose(Device.java:295)
	at thredTest2.SWTThreadTest2.main(SWTThreadTest2.java:31)

ich denke mal das liegt daran, wenn durch timerExec ein redraw ausgeführt wird, und kurz davor das fenster geschlossen wurde...
vll müsste man noch im runnable von timerExec abfragen, ob das canvas disposed ist?
 
Zuletzt bearbeitet:
ruf mal composite.layout() auf ... so macht er den inhalt nochmal neu und dadurch könnt es dann gehen!

Grüße Swoop
 
danke für den tipp. Das Problem, dass erst nach Größe ändern neugezeichnet wird, ist dadurch gelöst.
Allerdings bestehen noch die anderen Probleme...
 
Es funktioniert bei mir undzwar ohne flackern (liegt das daran, dass canvas irgendnen buffering macht?).
Normal brauchst du kein explizites Double Buffering. Wenn doch, dann schreibt man das normalerweise nicht selbst, sondern verwendet das Double Buffering Style Bit.

- Die Linien kommen erst, sobald ich einmal die größe des fensters geändert habe - warum ist das so und wie kann mans ändern?
In dem Code Sample ist mir eine Zeile verrutscht:
Java:
        shell.setSize(new Point(WIDTH, HEIGHT));
        shell.setLayout(new FillLayout());
        new MyCanvas(shell);
        shell.open();
- So wird der ja alle x millisekunden neuzeichnen, auch wenn keine änderung vorliegt - einfach so akzeptieren, oder kann man das verbessern?
Das war nur ein Beispiel um zu zeigen das es eben normalerweise nicht flackert. Wann und ob du manuell ein redraw aufrufen musst hängt davon ab was du eigentlich erreichen möchtest.

Bei deiner Lösung stimmt irgendwas noch nicht so ganz, ich habe gerade folgende Exception bekommen:
Das war auch nur ein Beispiel. Man muss auf isDisposed prüfen und darf dann natürlich auch kein timedExec mehr aufrufen.
 
Der 2te Thread ist Grundfalsch. Du musst im UI Thread zeichnen. Dann brauchst du im Normalfall auch kein Image. Ausserdem fällt dann das asyncExec um das redraw weg.
Beispiel (so sieht es doch besser aus, oder 😉 ):

Ich wollte das analog zu Swing machen:

Man hat einen hauptthread, der daten ändert und bei änderung dem EDT bescheid sagt, dass er neumalen muss
und einen EDT, der sich um die anzeige kümmert

nur kann man da wohl nicht einfach redraw aufrufen vom nicht-EDT-thread, sondern muss das ganze noch in ein asyncexec/timerexec, oder so wrappen...
Und evtl. werden redraw requests dann nichtmal zusammengefügt, wie es bei swing der fall ist.
 
Warum sollte das in Swing nicht so gehen?

Ich kann doch ein Modell-Objekt haben, dass einen bestimmten zustand hat.
Ich kann dann außerhalb des EDT das modell ändern und dem EDT dann mit repaint bescheid sagen, dass das modell sich geändert hat und er es neu zeichnen soll, oder ist das falsch?
Und ob das Modell seinen Zustand jetzt in einer Liste speichert, oder in einem BufferedImage (was ja im grunde auch als zweidimensionales array von farbwerten gesehen werden kann), sollte doch egal sein.
Das einzige was passieren könnte ist, dass der EDT das Modell in einem "zwischenzustand" sieht, was mir aber relativ egal ist für die Anwendungen bei denen ich das anwenden wollte.

Kannst du mir bitte nochmal erklären, was genau daran jetzt falsch ist?
 
Zeichnen muss Betriebssystem Resourcen allokieren, sogar wenn es sich um ein Image handelt. In Swing ist das unter bestimmten Vorraussetzungen anders (Headless Mode), in SWT aber immer so, daher ist es nicht so das du einfach ein 'Modell' veränderst. Du zeichnest in einem separaten Thread und das macht man weder in Swing, noch in SWT.
 

Zurück
Oben