1 2 3 Previous Next

malenkov

39 posts
malenkov

After JavaOne Blog

Posted by malenkov Oct 20, 2011

Recently, I visited the JavaOne conference held by Oracle. I was a speaker at the BOF session named "Embed HTML in Your JavaFX 2.0 Application". To my big surprise, only 15 visitors came. It would be interesting to hear their opinion, since this is my first experience of presenting in English. Despite this, I will publish several articles to promote the material from my session.

I really enjoyed the organization of the conference. Awesome! It was very interesting to meet colleagues with whom I communicated only through e-mail. It turned out that it is much easier to meet my friends from St. Petersburg in San Francisco, though we are living and working in one city.

malenkov

Reversy Ataxx Blog

Posted by malenkov Oct 21, 2010

My colleague has developed a totally new game on a hexagonal board. At the moment, the only one game set is available. While he together with his friends is perfecting their skills, we would like to create an implementation of this game for PC. I suggested that we use my implementation of the Minimaxalgorithm. To demonstrate its simplicity and flexibility, I created an Ataxx clone slightly modifying my Reversy game.

You can find the source code of the application here. However, for a start, I recommend you to distinguishbetween the Reversy and Ataxx implementations. The first difference is that I do not calculate the weights of the pieces on the Ataxx board by default. The second difference is that the move consists of two phases: choosing the piece and choosing its new position. Although the calculation depth for the Ataxx game is 2, I cannot win this algorithm.

   

Hopefully, I'll find some time to clear up the source code of the game.

Stay tuned!

I developed my first implementation of theReversi game onBASIC 20 years ago. Since then, I ported it on each programming language I studied: Turbo Pascal with graphics library and Turbo Vision, C with the Windows API, C++ with the ClanLib, Java AWTapplet, and Swing application with Java2D. Now I am ready to publish yet another implementation, on JavaFX Script.

   

This game allows to compete with one of the algorithms as well as to see how these algorithms compete with each other. The Sergey algorithm is based on the Minimax algorithm with a simple table of game piece weights where the calculation depth is 6. The Pavel algorithm is developed by my colleague, and I have got it from the official JavaFX site. But I had to add the following constructor:

public Board(GameBoard board) {
    this();
    Object dark = board.getFriend();
    Object light = board.getEnemy();
    int pos = 11;
    for (int y = 0; y < 8; y++, pos += 2) {
        for (int x = 0; x < 8; x++, pos++) {
            Object piece = board.getPiece(x, y);
            this.data[pos] = (piece == dark)
                ? PIECE_DARK
                : (piece == light)
                    ? PIECE_LIGHT
                    : PIECE_EMPTY;
        }
    }
    this.movePiece = PIECE_DARK;
    prepareMoves();
}

When I increased the calculation depth, I discovered that the search function started blocking the UI. The rationale is that the Timeline class invokes all functions from key frames on the thread which renders UI. I decided to override the abstract JavaTaskBase class and implement the RunnableFuture interface to avoid extra entities:

public class JavaTask extends JavaTaskBase, RunnableFuture {
  override function create() {
    this
  }
  override function run() {
    // do something
  }
}

However, I found out that the RunnableFuture interface must be implemented on Java, i.e. it is prohibited to use FX objects. It seems to me that in this case the JavaTaskBase class should not be an abstract class. It should be initialized via a public variable as in myJavaTask class:

public class JavaTask extends JavaTaskBase {
  public-init var runnable: RunnableFuture on replace {
    start()
  }

  override function create() {
    runnable
  }
}

Note that JavaFX does not support thread synchronization. Be careful!

If you know how to use theinvokeLater method of the EventQueue class to split a long tasks, you can use thedeferAction method of the FX class in the same way. This method invokes a function on the thread which renders the UI. Thus, you don't need synchronization in this case.

Also Baechul's Weblog will be useful.

original post

malenkov

Attentiveness Evaluation Blog

Posted by malenkov Jul 15, 2010

The attentiveness evaluation test in my driving school was easier than one I developed by using JavaFX. I made it harder by adding the next value randomly.

   

The test data are stored in the PersistentProperties class that combines the Storage and the Properties classes. When you create its instance, all the key-value pairs are loaded from the specified storage. This class provides utility methods to convert a string value into the expected type. For example,

  public function getInteger(key, default: Integer) {
    var value = get(key);
    if (value != null) try {
      return Integer.parseInt(value)
    }
    catch (exception) {
    }
    default
  }

This method takes a string value for a given key and tries to parse it as an Integer value. If the value is not found or it could not be parsed, the method returns the default value. I think, it might be helpful to have such utility methods in the Properties class. ThePersistentProperties class is used in this test as well as in the previous one. The application model based on this class is used to store the data when they are changed. Study the PersistentModel class in detail.

public class PersistentModel {
  def properties = PersistentProperties {
    source: "properties.txt"
  }

First, the data storage is initialized.

  public var misses = properties.getInteger("misses", 0) on replace {
    if (isInitialized(blocker)) {
      properties.put("misses", misses);
      properties.store()
    }
  }

Then the misses variable of theInteger type is declared. Note that when you change this variable, its new value is stored in the repository. The following simple method is designed to avoid overwriting the repository during class initialization. The blockervariable is declared as the last variable. It is not initialized during the initialization of other variables. So, during the initialization of the misses variable, theisInitialized(blocker) method returnsfalse. Instead of the blocker variable, it is possible to use any class variable that is declared after this one.

  public-read var passes = properties.getInteger("passes", 0);

The passes variable is declared with the public-read modifier and changes only within the PersistentModel class. Its all changes are made by the model, so that it is possible to omit the 'on replace' block.

  public-read var millis: Long;
  var millisAll = properties.getLong("millis", 0) on replace {
    if (passes > 0) {
      millis = millisAll / passes
    }
  }

The millis variable is used for the average time. However, it is necessary to know all the spent time and the number of the passed attempts to calculate it. Therefore, the auxiliary millisAll variable is declared and the average time is recalculated when it is changed.

  public-read var errors: Long;
  var errorsAll = properties.getLong("errors", 0) on replace {
    if (passes > 0) {
      errors = errorsAll / passes
    }
  }

The errors variable is used similar to the millis variable.

  public function pass(millis: Long, errors: Long) {
    properties.put("passes", ++passes);
    properties.put("millis",   millisAll += millis);
    properties.put("errors",   errorsAll += errors);
    properties.store()
  }

The pass function is used to change several variables simultaneously and to store their new values in the storage.

  var blocker = true;
}

Note that using JavaFX storages solves the problem with the unsigned JNLP applets, that I discovered earlier.

original post

Last half a year I was quite busy. My personal priority was to get a driving license. Among many exercises in my car driving school there was a psychological test consisting of different tasks to check your reaction. I developed one of them by using JavaFX. It examines how fast you can click.

   

I would like us to consider some localization issues by using my application as an example (see themain script and its russian properties). If a double hash sign ## is prefixed to a string literal, JavaFX substitutes the string literal with a localized one during running the application. It means that the localized strings will be selected from thefxproperties files for the current locale.

For example, for the following JavaFX string literal:

##"Average time"

an fxproperties file could contain the following declaration:

"Average time"="??????? ?????"

It is very convenient and reminds me the gettext library. However, if these string literals are too long, then thefxproperties files become difficult to maintain. JavaFX solves this problem by using shortcuts.

For example, for the following JavaFX string literal:

##[ABOUT]"Click and wait.\nClick again when color is changed"

an fxproperties file could contain the following declaration:

"ABOUT" = "???????? ? ?????.\n??? ????????? ????? ???????? ?????"

These shortcuts could be used for more complex parameterized strings as well.

For example, for the following JavaFX string literal:

##[FAILED]"{prefix}:"
"\n{%5d model.misses} misses;"
"\n{%5d model.passes} passes."

an fxproperties file could contain the following declaration:

"FAILED" = "%s:\n%5d ??? ?????????;\n%5d ??? ??????."

Note that variable names are not used in localization. It is important only to declare their types. You can reorder variables using the following notation:%[index$]type.

And the most important feature! While in Java SE you had to use native2ascii converter, JavaFX enables you to change the charset using the following command:

@charset "windows-1251";

It allows to create thefxproperties files in your favorite editor, even if it does not support UTF. This feature is very relevant for me.

JavaFX fxproperties files are much more convenient and more powerful than Java SEproperties files. For more information, see the tutorialand the language reference.

original post

Caching of an empty array is a well-known pattern to improve performance. However, it is difficult to use it in generified classes.

Out of curiosity, I created a custom implementation of the array creation method based on Array.newInstance. To cache empty arrays, I use synchronized WeakHashMap, which maps any given component type to a weak reference to the corresponding empty array. This is not the fastest way, but it does not lead to memory leaks.

private static final Map<Class<?>, Reference<?>> map =
         new WeakHashMap<Class<?>, Reference<?>>();

@SuppressWarnings("unchecked")
public static <T> T[] newInstance(Class<T> type,
                                        int length,
                                        boolean cache) {
    if (!cache || length != 0) {
        return (T[]) Array.newInstance(type, length);
    }
    synchronized (map) {
        Reference<?> ref = map.get(type);
        Object array = (ref == null) ? null : ref.get();
        if (array == null) {
            array = Array.newInstance(type, length);
            map.put(type, new WeakReference(array));
        }
        return (T[]) array;
    }
}

Note that the method returns an array of a given type. I used the SuppressWarnings annotation to suppress warnings about unchecked casts.

Consider the results of the test application. The left column shows the number of iterations. During each iteration, an empty array is created for every given component type. The middle column shows the average time of an empty array creation. The right column shows the average time of creating and retrieving the shared empty array. Frankly, I was surprised that the caching of a single array is slower than caching of several ones.

    
                                            
AttemptsAverage time (ns)
normalcached
149065676
104901813
100388961
1000392980
10000248223
10000027456

1 class per attempt

                                            
AttemptsAverage time (ns)
normalcached
12622278
1026169
10024764
100026263
1000025761
10000025862

88 different classes per attempt

Cached empty arrays can be useful for the JVM too. For example, when calling varargs methods without arguments a new empty array is created every time. I do not think that anyone expects such behavior.

What do you think?

malenkov

Along the River Blog

Posted by malenkov Nov 20, 2009

The subject of the next JFXstudio Challenge competition is a Holiday. What are you going to do on this holiday? How about having a trip along the river?

   

The image can be dragged with the mouse cursor, or you can use the cursor keys for navigation. Also the thumbnail can be used to select the most appropriate viewport.

The painting used in the application was created in 12th century by an artist, Zhang Zeduan. This painting, as well as its remake made in the 18th century, is available on the Wikipedia website. You can find more panoramic views on this site. For example,



 

Be patient! The application will start only when the image is loaded.

Study the scriptnow.

def url = FX.getArgument("url");

def image = Image {
  url: if (url != null)
    then "{url}"
    else "https://malenkov.dev.java.net/20091120/AlongTheRiver.jpg"
}

The code above is used to load the image by the specified URL. If it is not set, the default image is loaded.

class View extends Rectangle {
  def scale = bind scene.width / image.width;

  override var width  = bind scene.width;
  override var height = bind scene.height - image.height * scale;

  def maxX = bind image.width  - width  on replace { setX(x) }
  def maxY = bind image.height - height on replace { setY(y) }

  function setX(newX: Number) { x = if (newX < 0 or maxX < 0) 0 else if (maxX < newX) maxX else newX }
  function setY(newY: Number) { y = if (newY < 0 or maxY < 0) 0 else if (maxY < newY) maxY else newY }
}

The View class is used to specify the viewport bounds. It also processes the window events and adjusts the coordinates automatically.

  onMousePressed: function(event) {
    if (event.dragX != 0 or event.dragY != 0) {
      view.setX(x - event.sceneX);
      view.setY(y - event.sceneY)
    }
    x = event.sceneX + view.x;
    y = event.sceneY + view.y
  }

The method above is used to handle theonMousePressed and onMouseDragged events of the primary image.

  onMouseDragged: function(event) {
    view.setX(event.x - view.width  / 2);
    view.setY(event.y - view.height / 2)
  }

The method above is used to handle theonMousePressed and onMouseDragged events of the thumbnail.

def timer = Timeline {
  repeatCount: Timeline.INDEFINITE
  keyFrames: KeyFrame {
    canSkip: true
    time: 10ms
    action: function() {
      if (key == KeyCode.VK_UP)    view.setY(view.y - rate) else
      if (key == KeyCode.VK_DOWN)  view.setY(view.y + rate) else
      if (key == KeyCode.VK_LEFT)  view.setX(view.x - rate) else
      if (key == KeyCode.VK_RIGHT) view.setX(view.x + rate);
      rate *= 1.02
    }
  }
}

This timeline is used to change the coordinates according to the key pressed. Note that the rate increases gradually.

var rate: Number;
var key: KeyCode on replace {
  rate = 0.2;
  if (key != null)
    then timer.play()
    else timer.stop()
}

The timeline starts when the key is pressed and stops when the key is released. The rate sets to the initial state.

      ImageView {
        image: image
        cursor: HAND
        translateX: bind -view.x
        translateY: bind -view.y
        focusTraversable: true
        onKeyPressed:  function(event) { key = event.code }
        onKeyReleased: function(event) { key = null }
        onMousePressed: view.onMousePressed
        onMouseDragged: view.onMousePressed
      }

It is the primary image that is moved accordingly to the viewport coordinates. ThefocusTraversable variable is set to trueto handle the key events.

      Group {
        translateY: bind view.height
        transforms: Scale {
          x: bind view.scale
          y: bind view.scale
        }
        content: [
          ImageView {
            image: image
            smooth: false
            blocksMouse: true
            onMousePressed: view.onMouseDragged
            onMouseDragged: view.onMouseDragged
          }
          view
        ]
      }

A thumbnail is created by scaling. The viewport is shown over the thumbnail as a transparent rectangle.

original post

malenkov

Dodecahedron Blog

Posted by malenkov Oct 27, 2009

I've made the decision to participate in the JFXstudio Challenge competition. The subject of the competition is Five. Therefore, I decided to replace the squares with the pentagons ]]>sample that rotates the cube?

There is a limitation that is set for the competition: you may have only 30 lines or 3000 characters of code to do something cool. However, readability is still important to me, and I think, it should not be sacrificed. Therefore, my rotating dodecahedron sample consists of 3000 symbols and 130 lines. Consider the script now.

class Point { var x: Number; var y: Number; var z: Number; function rotateX(cos,sin) { def tmp = cos*y - sin*z; z = cos*z + sin*y; y = tmp } function rotateY(cos,sin) { def tmp = cos*x + sin*z; z = cos*z - sin*x; x = tmp } function rotateZ(cos,sin) { def tmp = cos*x - sin*y; y = cos*y + sin*x; x = tmp } }

The Point class contains the coordinates of a point in the three-dimensional space, and it provides methods to rotate the point around the coordinate basis for each axis. A class is exactly the same as the Point class in the rotating cube sample.

class Face extends Polygon { override var stroke = RED; override var strokeLineJoin = ROUND; override var strokeWidth = 2; var ps: Point[]; function update() { var z: Number; points = for (p in ps) { z += p.z; [p.x,p.y] } visible = z > 0 } }

The Face class is the polygon used to define a dodecahedron face. I do not use binding here to improve performance and to minimize the number of lines. When the update method is called, the sequence of the polygon points is changed and the required faces are become visible.

def g = 1 + Math.sqrt(5); def r = 100; def a = r*2/g; def b = r*g/2; def ps = [ Point { x:r y: r z: r } Point { x:r y: r z:-r } Point { x:r y:-r z: r } Point { x:r y:-r z:-r } Point { x:0 y: a z: b } Point { x:0 y: a z:-b } Point { x:a y: b z: 0 } Point { x:a y:-b z: 0 } Point { x:b y: 0 z: a } Point { x:b y: 0 z:-a } ]; def qs = for (p in ps) Point { x:bind-p.x y:bind-p.y z:bind-p.z }

Wikipedia describes the vertices of a dodecahedron in detail. I'd like to mention that each vertice of a dodecahedron has a paired vertice that is symmetric relative to the coordinate basis. That's why I used two sequences of points. The ps sequence is initialized and rotated by the application. The qs sequence contains the symmetric vertices which are initialized and changed automatically by using the binding mechanism.

def fs = [ Face { ps:[qs[1],qs[6],ps[7],ps[2],qs[5]] } Face { ps:[qs[0],qs[6],ps[7],ps[3],qs[4]] } Face { ps:[qs[0],qs[6],qs[1],qs[9],qs[8]] } Face { ps:[ps[2],ps[7],ps[3],ps[9],ps[8]] } Face { ps:[qs[1],qs[5],ps[4],qs[3],qs[9]] } Face { ps:[qs[0],qs[4],ps[5],qs[2],qs[8]] } Face { ps:[ps[2],ps[8],ps[0],ps[4],qs[5]] } Face { ps:[ps[3],ps[9],ps[1],ps[5],qs[4]] } Face { ps:[qs[8],qs[9],qs[3],qs[7],qs[2]] } Face { ps:[ps[8],ps[9],ps[1],ps[6],ps[0]] } Face { ps:[ps[4],ps[0],ps[6],qs[7],qs[3]] } Face { ps:[ps[5],ps[1],ps[6],qs[7],qs[2]] } ];

The dodecahedron surface consists of 12 faces. I wanted to add visual identification to each vertice, however, I couldn't, because of the competition's limitation that limits the amount of code lines.

var x = 0.002; var y = 0.006;

The code fragment above defines the angles by which rotation occurs around the x and y axes accordingly.

Stage { title: "Dodecahedron (JavaFX sample)" style: TRANSPARENT resizable: false scene: Scene { fill: null width: 6*r height: 6*r content: Group { translateX: 3*r translateY: 3*r content: fs cursor: HAND blocksMouse: true onMouseDragged: function(event) { x = if (-5

The code that declares the scene is very simple. A transparent window is created and the group of the dodecahedron faces is located in the center of the window. Basically, the code doesn't differ much from a rotating cube sample.

Timeline { repeatCount: Timeline.INDEFINITE keyFrames: KeyFrame { time: 40ms action: function() { if (x != 0) { def cos = Math.cos(x); def sin = Math.sin(x); for (p in ps) p.rotateX(cos,sin) } if (y != 0) { def cos = Math.cos(y); def sin = Math.sin(y); for (p in ps) p.rotateY(cos,sin) } for (f in fs) f.update() } } }.playFromStart()

Each 40 milliseconds the animation timeline rotates a half of a dodecahedron points along the x axis and then along the y axis. After the rotation occurred all the dodecahedron faces are updated.

original post

malenkov

Scores in detail Blog

Posted by malenkov Oct 6, 2009

Can you live without computer of Internet for two weeks? I definitely got crazy and forgot everything I knew about JavaFX. Nevertheless, in this blog I'll try to explain how the Scoreclass introduced in my previous post works.

For a start, consider the Digit class, an internal auxiliary class. It is used to scroll one number position. Observe how many images do we use for this class. In order to gradually change the figures from 9 to 0 and vice versa, we added an extra image, the same as the first one. If that's the case, we need to scroll the upper position as well.

https://malenkov.dev.java.net/20090804/details.png

The Digit class is initialized by two variables:

  var owner: Score;
  var digit: Digit;

The owner variable is always defined and it specifies the instance of the Score class that uses the current instance of the Digit class. The digitvariable is used to define the previous number position. Its value for the lowest position is null.

  def score: Integer = bind if (digit == null)
    then owner.score.intValue()
    else digit.score / owner.radix
    on replace {
      if ((score != 0) and (this == owner.digit)) {
        owner.digit = Digit {
          owner: owner
          digit: this
        }
      }
    }

The score variable contains the number, which lower position is used for rendering. For the lowest position it contains the integer part of the scorevariable of the Score class. For every other higher position it contains the result of dividing the score value of the previous position by the radix. If the current instance of the Digit class is the last registered, and the score value increases, then one more instance of the Digit class is created.

  def value: Integer = bind score mod owner.radix;

The value variable contains the digit of the particular radix.

  def delta: Number = bind if (digit == null) 
    then owner.score - score
    else if (owner.radix == (digit.value + 1))
      then digit.delta
      else 0;

The delta variable contains a fractional part of the score variable only if the current position of the number is the lowest or if the previous position changes its value from 9 to 0 or from 0 to 9.

  override var translateX = bind if (digit == null)
    then -owner.width
    else -owner.width + digit.translateX;

Override the translateXvariable to align number positions in that way, so the higher positions were located left.

  override function create() {
    Group {
      clip: Rectangle {
        width:  bind owner.width
        height: bind owner.height
      }
      content: for (i in [0..owner.radix]) {
        ...
      }
    }
  }

The create function defines a group of images with limited visibility. To better understand the logic of this function, comment the clip variable initializing before starting.

        Group {
          content: [
            ...
          ]
          translateY: bind owner.height * (i - value - delta)
          visible: bind (i != 0) or (score != 0) or (digit == null)
        }

An offset is calculated for each image. Besides, all the highest positions that have the zero value are invisible.

            Group {
              def group = Group {
                content: node
                scaleX: bind owner.width  / node.boundsInParent.width
                scaleY: bind owner.height / node.boundsInParent.height
              }
              content: group
              translateX: bind -group.boundsInParent.minX
              translateY: bind -group.boundsInParent.minY
            }

The code above is used to normalize the location of the created node. It is especially helpful when applied to the Text nodes. First, we create a group, then we scale it to the specific size, and after that, the group is aligned to the left and upper borders.

Now consider the Score class. It is far more simple. I would like to describe some of its variables.

  public var value: Integer on replace {
    if (value < 0) {
      value = 0
    }
    else {
      timer.stop();
      timer.keyFrames = at (1s) { score => value tween EASEBOTH }
      timer.playFromStart();
    }
  }

When the value variable is changed, the timer is set and then it starts so that the value of the score variable gradually changes to new value of the value variable. Note that the score value is the rendered one.

  override function create() {
    group
  }

  def group = Group {}
  var digit = Digit {
    owner: this
  } on replace {
    insert digit into group.content
  }

The digit variable contains the higher position added to the group. When this variable is changed, a new position is added to the group.

malenkov

Score your game Blog

Posted by malenkov Aug 3, 2009

In a game the score refers to the amount of points gained by a player or a team. Consider a JavaFX component that shows the score and enables its smooth changing.

   

The value variable of the Scoreclass starts a timeline on replace that smoothly changes the old value to a new one. The width andheight variables define a size of a character cell. The score representation depends on the radix variable that is 10 by default. Additionally, you can provide custom images for every digit by using the images variable. In that case, the radix variable will be automatically recalculated. If you do not want to create custom images you can use the creator function. It should create nodes to insert into the component. Note that the Score class is implemented to display right-to-left oriented numbers, so that new digit orders are added to the left.

Look at the Mainclass that shows how to use the score counter. The following code creates the binary representation with the default font settings:

Score {
  radix: 2
  width: 15
  height: 6
}

The following code creates the octal representation with the images:

Score {
  width: 30
  height: 45
  images: for (i in [0..7]) Image {
    url: "{__DIR__}{i}.png"
  }
}

The following code uses colored text nodes for every digit:

Score {
  def color = for (hue in [36..360 step 36]) Color.hsb(hue, .9, .9);
  creator: function(index) {
    Text {
      content: "{index}"
      fill: color[index]
      font: Font {
        name: "Arial Bold"
      }
    }
  }
}

Now look at the buttons. I used the Tile container to arrange them. This container automatically resizes nodes that implement the Resizable interface. However, it doesn't resize the buttons created by my custom wrapper of the Button class. To make my wrapper resizable I implemented the following methods:

class MyButton extends CustomNode, Resizable {
  ...
  def button = Button {
    width: bind width
    height: bind height
    ...
  }
  override function create() {
    button
  }
  override function getPrefHeight(width) {
    button.getPrefHeight(width)
  }
  override function getPrefWidth(height) {
    button.getPrefWidth(height)
  }
}

original post

malenkov

FX Mobilization Blog

Posted by malenkov Jun 17, 2009

My colleague has just returned from JavaOne and brought an HTC Diamond cell phone that supports JavaFX. Of course I couldn't stop but running my demos on it.

Here they are in the order as they appeared:
· Drawing Application
· Flying Letters
· Magnetic Balls
· Weather Widget
· Firework (We Salute You)
· Analog Clock
· First Layout Sample
· Drag-and-Drop Demo
· Reversi (Othello)

Those who know me will not be surprised to see the last demo. But this demo is still in progress...

 

The Weather Widget shows a loading error just because I did not have an Internet access at that moment. But let me assure you, the demo shows weather pretty well...

So far I failed to run the Controlsdemo although this sample runs on the mobile emulator without any problems. Well, I'll look into it later...

Original post here.

malenkov

My Preferences Blog

Posted by malenkov Jun 4, 2009

The JavaFX 1.2 SDK provides many useful utility classes such as the Properties class used to access and store name/value pairs or the Storage class used to store the data locally on the client system.

http://java.sun.com/products/jfc/tsc/sightings/images/webstart.small.jpg

So far the JavaFX programming language does not support hash tables to store data of any type, but you can always use the Properties class for this purpose. For a start, let's create methods to put and get numbers. For example,

  public function put(key: String, value: Number) {
    put(key, value.toString())
  }

  public function get(key: String, default: Number) {
    var value = get(key);
    if (value != null) try {
      return Number.valueOf(value)
    }
    catch (exception) {
    }
    default
  }

Note that the put method converts a number into a string and the get method tries to convert a string into a number. For primitive data types, this is done rather easily. For more complex cases, you can develop your own implementation of the Converter interface and use it to convert an object into a string and vice versa. Let it be your homework exercise.

Now add an ability to store properties automatically. I decided that at the moment of initialization, the class should read all properties from the storage and put them back when an application terminates. This task is very simple.

  var storage: Storage;

  postinit {
    storage = Storage {
      source: source
    }
    if (storage.resource.readable) {
      load(storage.resource.openInputStream())
    }
    FX.addShutdownAction(store)
  }

  function store() {
    if (storage.resource.writable) {
      store(storage.resource.openOutputStream(true))
    }
  }

Consider an example of how to use the Preferences class. Create a simple applicationthat stores the position and size of a window.Launch this application. Move the window, resize it and then close the application. Launch the same application again. You see that the window is located where you left it in the previous session. It is convenient, isn't it?

class PersistentStage extends Stage {
  def preferences = Preferences {
    source: "bounds"
  } on replace {
    def s = Screen.primary.visualBounds;
    width  = preferences.get("W", s.width  / 2);
    height = preferences.get("H", s.height / 2);
    x = preferences.get("X", s.minX + (s.width  - width)  / 2);
    y = preferences.get("Y", s.minY + (s.height - height) / 2);
  }
  override var x      on replace {preferences.put("X", x     )}
  override var y      on replace {preferences.put("Y", y     )}
  override var width  on replace {preferences.put("W", width )}
  override var height on replace {preferences.put("H", height)}
}

Later I will improve the Application class from the previous post by adding a support for properties storage.

original post

So the next version of JavaFX has been released. I developed an example that shows all the node-based UI controls that had been added to the API to replace the controls based on the Swing library. Note, that new UI controls are available on all platforms including mobile.

The following set of the controls is included in the current API:
TextBox: A control which displays and accepts input of text.
Button: A simple command button control.
ToggleButton: A control that possesses the ability to be selected.
RadioButton: A kind of a toggle button that has another appearance.
CheckBox: A tri-state selection control with a tick mark when checked.
Hyperlink: An HTML like text label that responds to rollovers and clicks.
Slider: A control that enables selecting a value by sliding a knob.
ProgressBar: A component used to show the progress of a task.
ProgressIndicator: A component that displays the progress in a form of a pie chart.
ListView: A simple list of items that can be editable.
ScrollBar: An element that enables the graphical content of a container to be scrolled.

You can learn more about these controls in the Powerful UI Capabilities With Node-Based Controls tutorial. This article discusses some performance aspects of the UI controls implementation and suggests how to employ them in your applications. First, run this example.

ScrollPane and performance

As you may see, the main window does not contain a scroll bar. However, you can see it in action in the ScrollPaneclass that supports a mouse wheel. The values of theboundsInParent variable defined in the Node class should be used in expressions to lay out the content. Never bind these values directly! The rational is that these values are updated many times per second and regular recalculations of expressions substantially decrease performance of your application. In order to resolve this problem, I created the Boundsclass. The coordinates and dimensions are updated only when the corresponding values actually changed. If you compile and run the application by using the Bounds class, you will see that the values with the prefix content are constantly updated, even if the values are not changed. Another recommendation is to remove println, because the debug printing decreases performance as well.

ScalePane

Games often require to show all the graphical content constantly. That's why, I developed the ScalePaneclass for these purposes. It is constructed similar to the ScrollPane class and preserves the aspect ratio. Try this example to explore it in action. In some rare cases you might want to disable preserving the aspect ratio, however, run this example to ensure that is looks ugly.

Application

Having reviewed a lot of samples, I realized that each model of the application deployment requires specific actions. All the possible actions are implemented in theApplicationclass. The application not only looks and works identically on the mobile device and in the full-screen mode, but additionally, it enables dragging the applet from a browser without pressing the Alt key. The main class of the example is very simple now.

Application {
  title: "JavaFX Controls"
  header: ImageView { ... }
  background: LinearGradient { ... }
  content: ScrollPane {
    background: Color.WHITE
    border: 10
    content: ...
  }
}

Refer to the source code of the Controlsclass for more infromation.

https://malenkov.dev.java.net/20090602/mobile.png

The Preferences API can be used by applications along with the installed security manager that enables using the preferences permission. However, WebStart-based applications cannot permit preferencesonly. You can permit all or deny all by using ajnlp-file. So, how to store user preferences for unsigned applications deployed through Java WebStart?

Java provides the Preferences API to store and retrieve user and system preference and configuration data. These data are stored persistently in an implementation-dependent backing store. Typical implementations use Windows Registry or UNIX flat files. The Preferences API is designed as the Service Provider Interface (SPI). Therefore, you can provide custom implementation of the PreferencesFactory class that will be loaded on demand. But you can't use the Preferences API with your custom implementation, because the static methods of the Preferences class throws SecurityException. Why?!

The PersistenceService class is provided to store data locally on the client system for JNLP-based applications running in the untrusted execution environment. So, it is possible to create a custom implementation of the Preferences class, but we cannot fully use its functionality, because static methods cannot be overridden.

That's why I have created the PersistencePreferences class that allows to store user preferences for unsigned applications deployed through Java WebStart. Additionally, I have created the MemoryPreferencesclass that can be used as a temporary storage while an application is running. It makes the preference operations transparent. Consider the AppletPrefsapplet that uses the following method to choose an appropriate node that holds the application preferences:

private static Preferences getPreferences() {
    Preferences prefs;
    try {
        prefs = Preferences.userRoot();
    }
    catch (SecurityException disabled) {
        try {
            prefs = new PersistencePreferences();
        }
        catch (Exception unavailable) {
            prefs = new MemoryPreferences();
        }
    }
    return prefs.node("Vendor Name").node("App Name");
}

Signed JNLP applet

 

The signed applet above enables all permissions in jnlp-file. On Windows it stores its preferences in the Windows Registry. You can check them using theregedit command.

Unsigned JNLP applet

 

The unsigned applet above stores its preferences by using the JNLP persistence service. Try to reload the page to ensure that all tabs and their content is saved.

Update: Hmm... This applet throws the BackingStoreException. Seems the reason is that the location of the jar-file differs from the web page location. This applet works here. What a pity! It was an interesting idea. But you can try to start the unsigned applet as a Java WebStart application. It works as expected:

http://java.sun.com/products/jfc/tsc/sightings/images/webstart.small.jpg

Simple unsigned applet

 

The applet above is the same as the previous one, but it is deployed as a usual applet. Therefore, it cannot access the JNLP services and does not reload data on the page restart.

original post

malenkov

Don't miss this Blog

Posted by malenkov Apr 12, 2009

I would like to discuss code conventions. In particular, the usage of the this keyword.

Recently we have argued with one of my colleagues again. I always use the this.name pattern to access class fields. Whereas he says that I should not differ from everybody else and use this to avoid shadowing only. Let me explain my position on the question.

The this keyword is commonly used to access the class fields, which are shadowed by a method or constructor parameter. For example,

public class Bean {
     private int value;
     public void setValue(int value) {
         this.value = value;
     }
     public int getValue() {
         return this.value;
     }
}

In the setter, you need to use thethis keyword because of the shadowing unless you are going to look for synonyms to name parameters. In the getter, thethis keyword can be omitted, but I never do so due to the following reasons.

First, one of the reasons for the creation of the Code Conventions for the Java Programming Language document was an improvement of code readability. I think that thethis.name identifier bears more clarity than justname does. If you follow such pattern, a code maintainer will quickly detect whether it is a class field or a local variable used.

Second, I do not like conditional rules, when the way you implement a standard action depends on some other conditions. Instead, I would rather prefer consistency. If class fields are accessed by using the this keyword in one case, it is reasonable to access class fields by using the same pattern in the rest of the situations.

Third, the this keyword allows developers to avoid hard-to-detect errors when they modify a method with a huge code and introduce a variable that shadows a class field. For example, let's declare the directoryvariable in the following method that is huge. The rest of the method will use this local variable instead of using a class field. As a result, the code works differently. There are no compiler-time errors, but the method results are unexpected at run-time.

class Location {
     private File directory;
     public void process(String mask) {
        ...
         Object object1 = directory;
         ...
         File directory = null;
         ...
         Object object2 = directory;
         ...
     }
}

Finally, I have to read a code using not only IntelliJ IDEA but some other tools as well. Quite often I need to review a code sent by my colleagues as either an HTML or a diff-file. In such cases it is rather difficult to detect the scope of the identifier if the code is huge. Look at the addImpl method of the Containerclass. How fast will you determine what the componentidentifier mean? How fast will you determine that thecomponent identifier does not correspond to thecomp method's parameter?

protected void addImpl(Component comp, Object constraints, int index) {
    synchronized (getTreeLock()) {
        /* Check for correct arguments:  index in bounds,
         * comp cannot be one of this container's parents,
         * and comp cannot be a window.
         * comp and container must be on the same GraphicsDevice.
         * if comp is container, all sub-components must be on
         * same GraphicsDevice.
         */
        GraphicsConfiguration thisGC = this.getGraphicsConfiguration();

        if (index > component.size() || (index < 0 && index != -1)) {
            throw new IllegalArgumentException("illegal component position");
        }
        checkAddToSelf(comp);
        checkNotAWindow(comp);
        if (thisGC != null) {
            comp.checkGD(thisGC.getDevice().getIDstring());
        }

        /* Reparent the component and tidy up the tree's state. */
        if (comp.parent != null) {
            comp.parent.remove(comp);
            if (index > component.size()) {
                throw new IllegalArgumentException("illegal component position");
            }
        }

        //index == -1 means add to the end.
        if (index == -1) {
            component.add(comp);
        } else {
            component.add(index, comp);
        }
        comp.parent = this;

        adjustListeningChildren(AWTEvent.HIERARCHY_EVENT_MASK,
            comp.numListening(AWTEvent.HIERARCHY_EVENT_MASK));
        adjustListeningChildren(AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK,
            comp.numListening(AWTEvent.HIERARCHY_BOUNDS_EVENT_MASK));
        adjustDescendants(comp.countHierarchyMembers());

        invalidateIfValid();
        if (peer != null) {
            comp.addNotify();
        }

        /* Notify the layout manager of the added component. */
        if (layoutMgr != null) {
            if (layoutMgr instanceof LayoutManager2) {
                ((LayoutManager2)layoutMgr).addLayoutComponent(comp, constraints);
            } else if (constraints instanceof String) {
                layoutMgr.addLayoutComponent((String)constraints, comp);
            }
        }
        if (containerListener != null ||
            (eventMask & AWTEvent.CONTAINER_EVENT_MASK) != 0 ||
            Toolkit.enabledOnToolkit(AWTEvent.CONTAINER_EVENT_MASK)) {
            ContainerEvent e = new ContainerEvent(this,
                                 ContainerEvent.COMPONENT_ADDED,
                                 comp);
            dispatchEvent(e);
        }

        comp.createHierarchyEvents(HierarchyEvent.HIERARCHY_CHANGED, comp,
                                   this, HierarchyEvent.PARENT_CHANGED,
                                   Toolkit.enabledOnToolkit(AWTEvent.HIERARCHY_EVENT_MASK));
        if (peer != null && layoutMgr == null && isVisible()) {
            updateCursorImmediately();
        }
    }
}

Filter Blog

By date: