Archive for the ‘Multi-Threading’ Category

JavaFX – Multi-Threading and updating a shape in a scene with a controlled delay

March 18, 2010

I came across a posting on the JavaFX general forum. I was intrigued by what the person was looking for, and by the suggested result. I decided to write my own solution, although I was too late to help the person as the others had already made suggestions. I prefer mine 🙂 Further to this, the problem posed by the person was similar to the problem I was having.

The Problem:

They want to update the width of a rectangle every second, based on a random value. This update should be a smooth animation, so it would be similar to sliding doors (in my mind anyway).

The initial response was to use Timelines to complete this. Now, I know that JavaFX is single threaded, but I am unable to make this work using Timelines (I was guessing two embedded Timelines). However I could not get this to work in a controllable manner and so started searching the web knowing I need to implement multi-threading behaviour.

After reading this excellent blog entry: http://blogs.sun.com/baechul/entry/javafx_1_2_async I set about creating my multi-threaded code to accomplish the task.

The basic theory here is you create a process / thread / task (whatever you want to call it) that will go off and get you the data you are looking for (in this case a random number which will be mapped to the rectangle’s width). Whilst in the process of getting this data you put the process to sleep, that is so there is a delay in you receiving the data. When you receive this data you can apply it to the rectangle, in this case, and at the same time apply the animation which will create the sliding door effect (this is my own interpretation of the effect).

To do this we need to create Java classes, a Java Interface, and JavaFX file.

In the first place create the class which performs the task, in this case the random number generation for the width:

SusantData.java

public class SusantData
{
    int value = 0;

    public double getSusantValue()
    {
        return 25 + java.lang.Math.random() * 50;
    }
}

With that done, we now need to create our process / thread / task class. This class actually controls the processing of the
thread, you start, pause and detect the status of the thread through this class. This is a JavaFX class, not a Java class.

MyTaskSusant.fx

public class MyTaskSusant extends JavaTaskBase, Progressable
{
    public-init var rData: SusantData;
    var peer: MyRunnable;

    override function create(): RunnableFuture
    {
        peer = new MyRunnable(rData, this);
    }

    override function setProgress(pro:Long)
    {
        progress = pro;
    }

    override public function start():Void
    {
        progress = 0;
        super.start();
    }
}

Ok, so we have the task class setup, we now need to create an interface which will be used by both our task class and thread, it is quite straight forward. This method allows us to update the progress of the thread between the two classes. You will have noticed that the task class implements both the interface and JavaTaskBase.

Progressable.java

public interface Progressable
{
    void setProgress(long progress);
}

The final class, MyRunnable directly controls the thread. Here we define the code around the run statement, and one of the most important bits is to put the thread to sleep to introduce our one second delay. In this case we simply get our random value and update the setProgress. In this example I want the width, I.e the random number, of the rectangle to change / morph / slide 100 times and hence placed the loop within the run method. You will notice, also, that within setProgress FX.deferAction() is used to invoke the threads setProgress because all events have to be dispatched in EDT (Event Dispatch Thread).

MyRunnable.java

public class MyRunnable implements RunnableFuture
{
    Progressable task;
    SusantData rValue;

    public MyRunnable(SusantData rdata, Progressable pi)
    {
        rValue = rdata;
        task = pi;
    }

    public void run() throws Exception
    {
        for (int i = 0; i < 100; i++)
        {
            Double t = rValue.getSusantValue();
            System.out.println("susantValue is now "+t);
            setProgress(t.intValue());
            Thread.sleep(1000);
        }
    }

    void setProgress(final long progress)
    {
        javafx.lang.FX.deferAction(new Function0<Void>()
        {
            public Void invoke()
            {
                task.setProgress(progress);
                return null;
            }
        });
    }
}

Now we have set all the “background” code up, we need to code the “stage” making reference to this code. Here is the main.fx, were we are instantiating the classes (SusantData and MyTaskSusant) and then define our objects (shapes etc). I have commented the code to try and explain what I am doing:

main.fx

var s:  SusantData = SusantData{};

var susantTask: MyTaskSusant;

var width: Integer = 10;
var rect : Rectangle = Rectangle
{
    width: width     //Initial start value
    height: 10;
    fill: Color.PINK;
    translateX: 100;
    translateY: 60;
}

var t: Timeline;

//This is a trigger, everytime the progress value
//changes, which is the random number
//generated by SusantData then the trigger is
//started. In the first instance it checks
//that the value generated is greater than 0
//(because this will occur at start up -
//own preference here).
//
//When the value of st is greater than 0 we create
//the Timeline instance which references
//rect, defines the time to run (1 second) and states
//that it should take the rectangle's
//width from its current value to the random
//value generated. This animation should EASEBOTH
//which is it slow to start and finish.
//
//With that complete, we play the Timeline and then
//set the rectangle's width to the new value.
//
var st = bind susantTask.progress on replace
{

    if (st > 0)
    {
        t = Timeline
        {
            def temp = rect;

            keyFrames:
            [
                KeyFrame
                {
                    time: 1s
                    values:
                    [
                        temp.width => susantTask.progress
                                   tween Interpolator.EASEBOTH
                    ]
                }
            ]
        }
        t.playFromStart();
        rect.width = st;
    }
}

// My stage onto which the sliding door performs.

Stage
{
    title: "Async Sample"
    scene: Scene
    {
        width: 300
        height: 100
        content:
        [
            VBox
            {
                content:
                [
                    //Text to display the value of the
                    //random number generated.
                    HBox
                    {
                        content: Text
                        {
                            translateY: 50
                            font: Font { size: 16 }
                            x: 50
                            y: 90
                            content: bind
                             "{susantTask.progress}"
                        }
                    }
                ]
            },
            HBox
            {
                content:
                [
                    //The start button to get this
                    //show on the road
                    HBox
                    {
                        content:
                        [
                            Button
                            {
                                translateX: 10
                                translateY: 10
                                text: "Start"
                                action: function()
                                {
                                    susantTask = MyTaskSusant
                                    {
                                        rData: s
                                        onStart: function()
                                        {
                                         println("Starting");
                                        }
                                    }
                                    susantTask.start();
                                }
                            }
                        ]
                    },
                    //The cancel button, which
                    //stops the thread / process
                    HBox
                    {
                        content:
                        [
                            Button
                            {
                                translateX: 110
                                translateY: 10
                                text: "Cancel"
                                action: function()
                                {
                                    susantTask.stop();
                                }
                            }
                        ]
                    }
                ]
            },
            //The sliding door, well sort of,
            //which is updated by the st trigger
            Group
            {
                content: bind
                [
                    rect
                ]
            }
        ]
    }
}

Have a play, what do you think?

Advertisements