I am trying to render a loading animation on top of a nattable. I use the OverLayPainter mechanism to draw a "glasspane" and some text on top of the table, and this works perfect:
public class MessageOverlay
implements IOverlayPainter {
....
#Override
public void paintOverlay(final GC gc, final ILayer layer) {
this.currentGC = gc;
this.currentLayer = layer;
if (visible) {
currentGC.setAlpha(200);
currentGC.fillRectangle(0, 0, currentLayer.getWidth(), currentLayer
.getHeight());
drawMessage();
if (withLoadingAnimation) {
showAnimation = true;
}
} else {
showAnimation = false;
}
}
}
However, the paintOverlay method is not called regularely but rather everytime the table changes.
To be able to display a smoothe animation, I added a new thread
final Thread animatorThread = new Thread(new Runnable() {
#Override
public void run() {
while (!Thread.interrupted()) {
try {
Thread.sleep(1000 / fps);
} catch (final InterruptedException e) {
break;
}
display.asyncExec(new Runnable() {
#Override
public void run() {
if (showAnimation && !currentGC.isDisposed()) {
final Image currentImage = getNextImage();
final int posX = currentGC.getClipping().width / 2
- currentImage.getBounds().width;
final int posY = currentGC.getClipping().height / 2
- currentImage.getBounds().height;
currentGC.drawImage(currentImage, posX, posY);
}
}
});
}
}
});
animatorThread.start();
As you can see it tries to access the graphics context this.currentGC set in the paintOverlay method. My problem is, that currentGC within the animatorThread is always disposed.
How can I a.) ensure that the context is not disposed in the thread or b.) solve this in an alternative way?
Thanks for the help.
You could try to create a new GC with the current NatTable instance and if needed pass the config from the passed in GC instance. Then you are in charge of disposing the GC instance and should not have the risk of a disposed GC outside your thread.
A simple example could look like the following snippet that simply shows the pane for 1000ms and then removes it again. You need of course to change the logic to be more dynamic with regards to your loading operation then:
AtomicBoolean paneThreadStarted = new AtomicBoolean(false);
...
natTable.addOverlayPainter(new IOverlayPainter() {
#Override
public void paintOverlay(GC gc, ILayer layer) {
if (this.paneThreadStarted.compareAndSet(false, true)) {
Display.getDefault().asyncExec(new Runnable() {
#Override
public void run() {
GC currentGC = new GC(natTable);
currentGC.setForeground(GUIHelper.COLOR_WHITE);
currentGC.setBackground(GUIHelper.COLOR_BLACK);
currentGC.setAlpha(200);
currentGC.fillRectangle(0, 0, layer.getWidth(), layer.getHeight());
String load = "Loading data ...";
Point textExtent = currentGC.textExtent(load);
currentGC.drawText(load,
layer.getWidth() / 2 - textExtent.x / 2,
layer.getHeight() / 2 - textExtent.y / 2,
true);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
currentGC.dispose();
natTable.redraw();
}
});
}
}
});
This way you are able to show the pane again by changing the AtomicBoolean from the outside:
Button showPaneButton = new Button(buttonPanel, SWT.PUSH);
showPaneButton.setText("Show Pane");
showPaneButton.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
this.paneThreadStarted.set(false);
natTable.redraw();
}
});
Related
I have an application that can create a rectangle that decreases in size for example a lapse of time of 10 sec, but here is when I try to shrink the rectangle, the window bug (nothing is displayed in the scene) and wait until the countdown is finished to stop bugging (and then display the rectangle not diminished).
I tried to find on the Internet the equivalent of repaint in Swing but not average: /
this.requestLayout () -> I found this on the internet but it does not work.
Here is my code of my countdown:
public class Compteur {
DemoBorderPane p ;
public DemoBorderPane getPan() {
if(p==null) {
p = new DemoBorderPane();
}
return p;
}
public Compteur() {
}
public void lancerCompteur() throws InterruptedException {
int leTempsEnMillisecondes=1000;
for (int i=5;i>=0;i--) {
try {
Thread.sleep (leTempsEnMillisecondes);
}
catch (InterruptedException e) {
System.out.print("erreur");
}
System.out.println(i);
getPan().diminuerRect(35);
}
}
}
There is my Borderpane code :
public class DemoBorderPane extends BorderPane {
private Rectangle r;
public Rectangle getRect() {
if(r==null) {
r = new Rectangle();
r.setWidth(350);
r.setHeight(100);
r.setArcWidth(30);
r.setArcHeight(30);
r.setFill( //on remplie notre rectangle avec un dégradé
new LinearGradient(0f, 0f, 0f, 1f, true, CycleMethod.NO_CYCLE,
new Stop[] {
new Stop(0, Color.web("#333333")),
new Stop(1, Color.web("#000000"))
}
)
);
}
return r;
}
public void diminuerRect(int a) {
getRect().setWidth(getRect().getWidth()-a);
int c= (int) (getRect().getWidth()-a);
System.out.println(c);
this.requestLayout();
//this.requestFocus();
}
public DemoBorderPane() {
this.setBottom(getRect());
}
}
There is my Main code :
public class Main extends Application {
private DemoBorderPane p;
public DemoBorderPane getPan() {
if(p==null) {
p = new DemoBorderPane();
}
return p;
}
#Override
public void start(Stage primaryStage) {
Compteur c = new Compteur();
try {
//Group root = new Group();
Scene scene = new Scene(getPan(),800,600);
//scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
//root.getChildren().add(getPan());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
try {
c.lancerCompteur();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
/*Son s = null;
try {
s = new Son();
} catch (LineUnavailableException | IOException | UnsupportedAudioFileException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
s.volume(0.1);
s.jouer();
c.lancerCompteur();
s.arreter();*/
}
}
Thank ;)
As long as you keep the JavaFX application thread busy it cannot perform layout/rendering. For this reason it's important to make sure any methods that run on the application thread, like e.g. Application.start or event handlers on input events return fast.
lancerCompteur however blocks the application thread for 5 seconds so the only result you see is the final one after the method completes.
In general you can run code like this on a different thread and use Platform.runLater to update the ui.
In this case you could take advantage of the Timeline class which allows you to trigger an event handler on the application thread after a given delay:
#Override
public void start(Stage primaryStage) {
Scene scene = new Scene(getPan(), 800, 600);
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), event -> {
getPan().diminuerRect(35);
}));
timeline.setCycleCount(5);
timeline.play();
primaryStage.setScene(scene);
primaryStage.show();
}
You also use different instances of DemoBorderPane in your Main class and the Compteur class; the Rectangle shown in the scene was never subject to an update.
there's no need to call requestLayout in diminuerRect. This happens automatically when the Rectangle's size is modified.
Lazy initialisation is pointless, if you know for sure the getter will be invoked during the object's creation. DemoBorderPane.getRect is invoked from it's constructor so moving the initialisation to the constructor would allow you to get rid of the if check without affecting functionality.
I develop an application and I want to make a circle blink when you click on a button. I have 8 circle on my view and I want them blinking separatly. I use this code :
public void blink(final View id, final int position, final boolean bool) {
final Handler handler = new Handler();
Thread th = new Thread(new Runnable()
{
#Override
public void run() {
final int timeToBlink = 250;
try {
Thread.sleep(timeToBlink);
} catch (Exception e) {
e.printStackTrace();
}
handler.post(new Runnable()
{
#Override
public void run()
{
if (id.getVisibility() == View.VISIBLE) {
id.setVisibility(View.INVISIBLE);
} else {
id.setVisibility(View.VISIBLE);
}
blink(id,position,true);
}
});
}
});
th.setName(Integer.toString(position));
aThread.add(th);
th.start();
where id is the id of the circle
but I can't stop blink with th.interupt
anyone can help me please ?
Thank to #jibysthomas i fixe my problem i use this link and i made this :
final Animation animation = new AlphaAnimation(1, 0);
animation.setDuration(250); // duration - half a second
animation.setInterpolator(new LinearInterpolator());
animation.setRepeatCount(Animation.INFINITE);
animation.setRepeatMode(Animation.REVERSE);
and i call this annimation on my circle.
Thanks a lot to jibysthomas
Can you guys tell me how can I execute function updateTiles() ONLY after all nodes' actions in function slideTiles(String s) are finished? I tried adding some while loop but it freezes whole program. After running functions one after another the animations just don't show up.
public boolean ccTouchesEnded(MotionEvent event)
{
CGPoint location = CCDirector.sharedDirector().convertToGL(CGPoint.ccp(event.getX(), event.getY()));
x2=location.x; y2=location.y;
float diffY = y1-y2;
float diffX = x1-x2;
if (Math.abs(diffX)>Math.abs(diffY)&&diffX<0)
move="right";
else if (Math.abs(diffX)>Math.abs(diffY)&&diffX>=0)
move="left";
else if (Math.abs(diffX)<=Math.abs(diffY)&&diffY<0)
move="up";
else if (Math.abs(diffX)<=Math.abs(diffY)&&diffY>=0)
move="down";
int row=(int)(y1/TILE_SQUARE_SIZE);
int column=(int)(x1/TILE_SQUARE_SIZE);
slideTiles(move);
//wait for animations to finish
int sum=0;
boolean actionDone=false;
while (!actionDone)
{
for (CCNode c : tilesNode.getChildren())
{
sum+=c.numberOfRunningActions();
}
//String s=sum+"";
//statusLabel.setString(s);
if (sum==0){
actionDone=true;
break;
}
sum=0;
}
updateTiles();
return true;
}
//////
public void slideTiles(String move)
{
// Increment the moves label and animate the tile
CCBitmapFontAtlas moveslabel = (CCBitmapFontAtlas) getChildByTag(MOVES_LABEL_TAG);
moves++;
moveslabel.runAction(CCSequence.actions(
//CCDelayTime.action(0.25f),
CCScaleTo.action(0.2f, 6/5f),
//CCDelayTime.action(0.25f),
CCScaleTo.action(0.2f, 5/6f)
));
moveslabel.setString("Moves:\n " + CCFormatter.format("%03d", moves ));
if (move.equals("up"))
{
for (int start=NUM_COLUMNS; start<2*NUM_COLUMNS; start++)
for (int y=start; y<NUM_COLUMNS*NUM_ROWS; y+=NUM_COLUMNS)
{
if (tileNumbers[y]!=EMPTY)
{
for (int f=start-NUM_COLUMNS; f<y; f+=NUM_COLUMNS)
{
if (tileNumbers[f]==EMPTY)
{
//y->f
CGPoint moveTo = tilesNode.getChildByTag(f).getPosition();
CCMoveTo movetile = CCMoveTo.action(0.25f, moveTo);
CCSequence movetileSeq = CCSequence.actions(movetile);//CCCallFunc.action(this,"stopAction"));
tilesNode.getChildByTag(y).runAction(movetileSeq);
tileNumbers[f]=tileNumbers[y];
tileNumbers[y]=EMPTY;
break;
}
}
}
}
}
Edit: Is this kind of solution correct? It works surprisingly well.
public boolean ccTouchesEnded(MotionEvent event)
{
...
...
slideTiles(move);
float time = 0.25f+0.05f; //0.25f is time of moveTo action for each tile
CCDelayTime delay = CCDelayTime.action(time);
CCCallFunc cA = CCCallFunc.action(this, "updateTiles");
CCSequence se = CCSequence.actions(delay, cA);
runAction(se);
return true;
}
Generally, you shouldn't use while loop to wait in UI thread , it will cause big problem you have saw it.
You should use Thread to do this waiting job.
new Thread(new Runnable() {
#Override
public void run() {
while(true){
//here you wait forever doesn't matter the UI thread.
if(everythingDone) {//here to add your condition
new Handler().post(new Runable() {
#Override
public void run() {
//Here you can do what you want and will be done in UI Thread
}
});
}
}
}
}).start();
I am doing a project that need to get page content from web page in Java, and sometimes, I need to execute some javascript on the web page and them get the modified content. So I choose to use SWT tool. Here is part of the code:
public void run(){
Display.getDefault().asyncExec(new Runnable(){
public void run(){
display = Display.getDefault();
shell = new Shell(display,SWT.CLOSE | SWT.MIN |SWT.TITLE);
shell.setText("Web Page");
shell.setSize(1024,768);
browser = new Browser(shell, SWT.NONE);
browser.setBounds(0, 0, 1010,700);
browser.addProgressListener(new ProgressListener() {
#Override
public void completed(ProgressEvent event) {
boolean success = browser.execute(script);
if(success || script.length()==0){
model.setHtml(browser.getText());
}
shell.dispose();
}
#Override
public void changed(ProgressEvent event) {
}
});
browser.setUrl(url);
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
});
}
And I am calling the the run() function within a class worker which extends from SwingWorker. The worker class is defined like this:
public class worker extends SwingWorker<Object,Integer>{
private ScrapingModel model;
private ScrapingView view;
private int[] indices;
public worker(ScrapingView v, ScrapingModel m, int[] ins){
model = m;
view = v;
indices = ins;
}
#Override
protected Object doInBackground() throws Exception {
int length = indices.length;
for(int i=0;i<indices.length;i++){
int index = indices[i];
//here call the run() function, sorry I skipped some code
publish((i+1)*100/length);
}
return null;
}
#Override
protected void process(List<Integer> chunks) {
for (Integer chunk : chunks) {
view.progressBar.setValue(chunk);
}
}
}
Here is the problem: the code wthin Display.getDefault().asyncExec never executes when I call the run() function in worker class. However, if I tried to call run() outside worker class, it could be executed. Any ideas?
Instead of wrapping the Runnable in an asyncExec
Display.getDefault().asyncExec(new Runnable(){
// swt code to open shell
});
Create a new Thread
Runnable r = new Runnable(){
// swt code to open shell
}
Thread t = new Thread(r);
th.start();
What im trying to do is pretty simple, I want to show the steps of an algorithm on the screen, hence why im trying to combine repaint() with sleep(), but I am doing it wrong, Id love it if someone knows enough about it to firstly explain whats wrong with this code, and secondly, what do i do to make it work...
thanks!
in summery, what this code was meant to do is paint 10 red vertices, then balcken em one by one in intervals of 200 milliseconds.
here's the code:
public class Tester {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ShowGUIGraph();
}
});
}
private static void ShowGUIGraph() {
JFrame f = new JFrame("something");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel p=new JPanel();
p.setLayout(new BorderLayout());
p.add(BorderLayout.CENTER,new SomePanel());
f.add(p);
f.setPreferredSize(new Dimension(800,600));
f.pack();
f.setVisible(true);
}
}
public class SomePanel extends JPanel {
private static final long serialVersionUID = 1L;
LinkedList<Vertex> vertices=new LinkedList<Vertex>();
public SomePanel () {
for (int i=0;i<10;i++) {
Vertex v=new Vertex(i);
v.setLocation(20+30*i, 20+30*i);
vertices.add(v);
}
traverseVerticesRecoursive(0);
traverseVerticesNonRecoursive();
}
public void traverseVerticesRecoursive(int i) {
if (i>=vertices.size()) return;
vertices.get(i).setColor(Color.black);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
repaint();
traverseVerticesRecoursive(i+1);
}
public void traverseVerticesNonRecoursive() {
for (int i=0;i<10;i++) {
vertices.get(i).setColor(Color.red);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
repaint();
}
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (int i=0;i<vertices.size();i++) {
vertices.get(i).paintVertex(g);
}
}
}
public class Vertex {
private int x,y,tag,r=20;
private Color color=Color.red;
Vertex (int i) {
tag=i;
}
public void setLocation(int x0,int y0) {
x=x0;
y=y0;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setColor(Color c) {
color=c;
}
public boolean colorIs(Color c) {
return (color.equals(c));
}
public void paintVertex(Graphics g) {
g.setColor(color);
g.fillOval(x,y,r,r);
g.setColor(Color.BLACK);
g.drawOval(x,y,r,r);
g.drawString(""+tag, x+r/2, y+r/2+4);
}
public int getR() {
return r;
}
}
Do not sleep in the Event Dispatch Thread; this will cause the GUI to freeze. For animation, use an EDT-friendly utility class, such as javax.swing.Timer.
Just a few ideas that might make your code cleaner:
In your SomePanel class, put the traversing code in a method out of the constructor. Constructors are intended for initializing fields.
First launch your static GUI, then spawn a worker thread to do the updates via the previous method (this would be your small "engine"). In this thread is were you can call sleep.
In your traverseVerticesRecoursive method, do only the repaint on the UI thread, and the status update on your worker thread.
Tha main modification you should do is not to block the GUI thread with sleep calls, as they have told you in the first answer.
Thread.sleep is a long running task. When you a running such a task in the EDT it blocks all repaint requests from being executed. All repaint requests which are pending and which were sent during the sleep phase are queued for future processing.
As a result when the EDT comes out of the sleep phase it coalesce all such repaint request (if coalescing is enabled which is the default property) into a single repaint which gets executed. If coalescing is not enabled then all queued request are executed serially without any time gap in between. As a result it seems that the UI did not update.
To correct the situation use a timer which triggers periodically after specific intervals of time.
Guy, you could use a new Thread differ with EDT thread to make an animation. For example,
void play() {
Thread thread = new Thread() {
#Override
public void run() {
game();
}
};
thread.start();
}
void game() {
for (; ; ) {
switch (state) {
case GameData.ANIMATING:
// call some function as repaint() to update GUI
break;
case GameData.GAME_ENDED:
return;
default:
break;
}
diffTime = System.currentTimeMillis() - beforeTime;
sleepTime = delay - diffTime;
sleepTime = (sleepTime < 0) ? 0 : sleepTime;
Thread.sleep(sleepTime);
}
}