Android – UK Highways Agency Traffic App

I have been getting back into Android programming again. To easy myself back into it and also force myself to use Android Studio I have put together a nice road traffic application. This kind of app covers a lot of the Android API so it’s a great way to update myself on the changes to the API.

The code is up on github here.

Let’s cover some of the interesting stuff.

Recycler List View

So this uses the new Android list component, the RecyclerView. In my previous post you can see my prototyping of the list as I wanted to get it right. I think it turned out okay and it’s a better example than most of the ones kicking around the net. Any dummy can built a list with one text item. It’s when you want to have icons and multiple text lines that the noddy demos than people post all fall down. So here is a decent example of something you might actually want to build.

list

I am using a image cache to cache the severity icons. You will see that the icons are categorised by type too. I just need to get around to generating some images.

Data Loader

The data is loaded using an RSS parser library I built a while back and have been meaning to use for some time. Data is pulled by a background task using Androids Loader API. This is the recommended component to do this now. In the past I had to roll my own threaded component.

Navigation Drawer

I want to do a bit more with this but in the meantime this is the drawer generater from the standard demo provided with Android Studio. I have tweaked it to load a list of traffic regions defined my an OPML file read locally to the application.

drawer

This is a nice way to this do this. The regions are not hard coded into the code.

Easy CP

My content provider library really shines here. It was so easy to define the domain object and write the code to read and write to persistence. If you haven’t already seen it check out EasyCP on github.

Android Studio

Finally, my take on Android Studio. Meh..it’s okay. Is Eclipse a better IDE? Yes. Can I understand why Google might want to have their own IDE? Yes, I think I can see why. I guess it’s about control and they don’t have any control over Eclipse. Now they’ve got control of the IDE and the build system. Despite the fact that 1998 called and wants it’s UI back. Also, just remember IntelliJ are not going to hand the over their core business to Google so don’t expect to be able to load in any old java project to AS any time soon. Google are always going to have to cripple this IDE in some fashion to avoid stepping on IntelliJ toes. Writing this application I had to switch back and forth between Eclipse and AS which was a bit of a pain in the arse. Anyway, we’ll see where they go with this.

LibGDX – Demos Updated for LibGDX 1.2.0

After some prompting I have finally got around to updating my LibGDX extenstion library and associated demos. The core library has been modified to pull the LibGDX library from the default maven repository at version 1.2.0. The demos have been updated accordingly to match the adjusted API. You can find all it in the usual place here.

Update

The poms for the Android targets were not correct. I have updated them. To install type:

mvn -Pandroid install

Web: Spring 4 & Thymeleaf Hot Cache

You might have run into a problem with using Thymeleaf and Spring 4 configuration where your changes to a Thymeleaf template are not immediately reflected on the page after refreshing it. Quite annoying especially as it worked ok with JSP’s right? To fix it adopt the following

/**
 * Web configuration.
 *
 */
// Marks this class as configuration
@Configuration
// Specifies which package to scan
@ComponentScan("com.netthreads.example")
// Enables Spring's annotations
@EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter
{
	public static final String DEFAULT_PREFIX = "/WEB-INF/templates/";
	public static final String DEFAULT_SUFFIX = ".html";
	public static final String DEFAULT_MODE = "HTML5";
	public static final String[] DEFAULT_VIEW_NAMES =
	{
		"*"
	};

	// Hot re-load, false=will reload changes.
	public static final boolean DEFAULT_CACHE = false;

	@Bean
	public ServletContextTemplateResolver templateResolver()
	{
		ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
		resolver.setPrefix(DEFAULT_PREFIX);
		resolver.setSuffix(DEFAULT_SUFFIX);
		resolver.setTemplateMode(DEFAULT_MODE);

		return resolver;
	}

	@Bean
	public SpringTemplateEngine templateEngine()
	{
		SpringTemplateEngine engine = new SpringTemplateEngine();
		engine.setTemplateResolver(templateResolver());

		// Need this for hot re-load
		if (!DEFAULT_CACHE)
		{
			engine.setCacheManager(null);
		}

		return engine;
	}

	@Bean
	public ViewResolver viewResolver()
	{
		ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
		viewResolver.setTemplateEngine(templateEngine());
		viewResolver.setOrder(1);
		viewResolver.setCache(DEFAULT_CACHE);
		viewResolver.setViewNames(DEFAULT_VIEW_NAMES);

		return viewResolver;
	}

	@Bean
	@Description("Spring message resolver")
	public ResourceBundleMessageSource messageSource()
	{
		ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
		messageSource.setBasename("messages");

		return messageSource;
	}

	/**
	 * Add our static resources folder mapping.
	 *
	 */
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry)
	{
		registry.addResourceHandler("/static/**").addResourceLocations("/static/");
	}
}

See the bits concerned with DEFAULT_CACHE. Copy these and you will find your changes reflected right away just like good old JSP!

Java – Ticket Checker Bot

Recently I was out-gunned in my ability to mash the keyboard fast enough and get past the capchas quick enough to nab myself some sought after tickets to Kate Bush in concert. There are rumours that there might be a second round of tickets released at some point. I’m not sure that’s going to turn out to be true but just in case it is I thought I would turn my failure into action and write an application which  will check for me and send me an email should tickets suddenly become available. It’s also an good excuse to learn some new things with Guice , the Java email API and velocity  (a template engine for Java).

In the end I created a wee application which has a whole load of interesting stuff in it. I can see it might come in useful for me and maybe others.

I wanted something I could set to run periodically and which would notify me by email.

I initially started using selenium but soon realised that I didn’t need a browser to navigate and scan a target site so settled on htmlunit instead.

The email component is setup to use gmail as a smtp gateway so you will need an gmail account with two-step validation switched off.

It’s totally over-engineered of course (you could probably do it in about 10 lines of perl no doubt) but that’s not the point is it?

Modules

The application has the concept of module (separate from the Guice module). These are custom classes which implement an operation of some kind which returns a list of URL links.

A module subclasses from the ModuleBot class and implements the interface:


public interface BotModule
{
   /**
   * Run module.
   *
   */
   public void run();

   /**
   * Return list of valid links.
   *
   * @return links.
   */
   public List<String> getLinkStatus();
}

In my sample I navigate to the gigsandtours website and look for the presence of a ‘buy ticket’ button. You could make it anything your were watching for.

A module is added to run by the application by adding it to the loadModules method in the TicketBot class i.e.

/**
* Load site modules.
*
* @param modules
* @param client
*/
private void loadModules(List<BotModule> modules, WebClient client)
{
   modules.add(new ModuleGigsAndTours(client));
}

This is the current definition which takes the custom module I wrote for checking the gigsandtours website.
Email

You will need to fill in your gmail credentials in the application-live.properties file. I have put dummy placemarkers in for the moment. Since this will be running on your own machine noone is going to be able to access it. If they can access your machine to look at your gmail password in this file you are already stuffed!

The application uses velocity as a template engine to build a notification email. The advantage of this is that the email layout is defined in the template and not hard-coded into the application. Take a look at the sample.

Build And Run

The component uses Maven.

To build issue the command line:

**mvn install**

This will create a zip file of the component distribution. Unzip this file to your target machine and modify the properties to match your email etc.
For Windows there is a batch file for Linux a shell file. For Windows create a periodic task to run the batch file and for Linux setup a cron job.

The project and code is up in github under the Apache 2.0 license.

 

 

LibGDX: Noiz2-gdx Vector Shoot Em Up

I have been noticing that there are quite a few instances of my original port of Kento Chos esoteric shooter – Noiz2 available. It has made it’s way across the internet on various app stores mostly unchanged which is fine by me. A few people have added adverts. Good luck to them.

Noiz2

Anyway, just to keep ahead of the game a bit I thought it was time to lavish some love on the noiz2 codebase and see if I couldn’t bring something new to the party. With this in mind I have mavenized it, ported it over to use LibGDX and put it up on Github. This took most of this weekend but I think I have almost got there.

Noiz2

The main new additions are:

It is not finished. I have not wired the preferences or the persistence of what levels the player has completed. Also the Android platform hasn’t been added yet.

The most interesting thing about the porting procedure was that it was pretty painless. I had abstracted the graphics and control elements previously as I had OpenGL and Canvas renderers available for the Android version. Wiring in the GDX elements was a complete doddle.

Future improvements

Code is here.

Update #1

I have got persistence of the game state and some of the original setting like sound and volume working with the LibGDX ‘Preferences’ class. Not so hard after all. The settings screen is a little flaky though. I have also started to tidy up a lot of the game text which was a bit wonky looking. One thing to note is I have ditched the original bitmap logo as I just don’t think it looked very good. I am trying to improve the look and feel overall rather than go for a ‘classic’ port of the game. I have, after all, already done that.

Update ‘#2

Added a nice menu. Can’t get it to run on Android yet.

Update #3

Working on Android. Looks good. Persistence of settings not working properly. I plan to fix this and then publish as an update.

Java: Standard RSS and UK Highways Agency RSS Pull parsers on github

I have been looking at GWT again recently and was considering trying to write a traffic application again. I wanted to generate some traffic incident data so I dug around into some old code which I had knocking around which parsed UK Highways Agency incident RSS.

The parser is a pull-parser. Pull parsers are fast and can be cancelled mid operation unlike SAX and DOM parsers which will block while they do their thing. This makes pull parsers ideal for Android. I did have a bit of search but this library appears to be unique in that I haven’t seen any RSS pull parsers elsewhere which is probably because they are a bit onerous to write.

I have separated out the RSS part into it’s own library and made a new component which just does HA RSS traffic incidents.

So, enjoy. There are unit tests which show how to use them.

Standard OPML and RSS parser here.

Highways Agency RSS parser here.

The license is Apache 2.0.

LibGDX: Demos on Github

I have made a start on fully Mavenizing my LibGDX demos and placed them up on Github.

The good news is that the core netthreads-libgdx library and the demos have been converted to use the latest version of LibGDX (0.9.9 at the time of writing). Taking the latest build from the LibGDX repo should mean that it will be easier to keep these up to date with changes to Scene2d etc.

I used the LibGDX archetype so you should have no problems building these once you have jumped through a few (minor I hope) hoops to install some dependencies on non-maven-available jar files like the tween-engine and fixtureatlas. There are detailed instructions. If you are having problems make a comment/email me or raise an issue on the issues list.

The core library is here. You will need this for the demos.

The demos up so far are:

Simple Shooter

This is the same simple shmup as before but with a lot of code tidied up. It is here.

Menu

Simple Shooter

Main

Simple Shooter

Box2d-Test

A straightforward Box2d demo with a central rotating fixture, falling objects and nice fading labels. It is here.

box2d-test
 

Box2d-Bumpers

Bumper walls, anti-gravity, tumbling blocks and flashing psychedelics..what’s not to like? Shows how to detect collisions between box2d elements and how to handle them.

box2d-bumpers

Putting these all the once place I have discovered a ton of bugs which I have had a chance to go through and fix. Later demos fixed stuff which I never went back and applied to the earlier ones like SimpleShooter so this has been a great exercise. It’s even given me a bit of a kick to write some more.

NOTE: All the LibGDX demos linked on this blog go to GitHub now. It is easier to manage them all in the one place. The new demos have a bunch of new concepts in them like the removal of the old Singleton patterns with Google Guice managing this stuff instead. Also it’s Maven from here on. If you are familiar with the old code and build mechanism then you’ll just have to come along for the ride. I promise you’ll learn stuff that will benefit you.

JavaFX: Java OSC library and JavaFX-based OSC Router

As part of my Tonome LibGDX project I decided to convert the LibGDX application to use OSC messages instead of triggering samples internally to the application.

Tonome2

The application could broadcast OSC note on and off messages to a Puredata instance and map that down to MIDI with a Puredata script. The first part of that was to use a Java OSC library that I would build into the application.

I had a look at the existing Java OSC libraries available but I couldn’t use them as they relied on creating a new instance of each message for each message sent. This is a no no with Android as you would have the garbage collector running. The OSC specification is up on the OSC website so why not try and write my own using a pooled approach where we reuse messages sent. The result is the osc-common and osc-network components.

osc-common

This is an almost fully compliant implementation of the OSC message spec. There are bits missing but for my purposes nothing essential.

The library like all the components is fully mavenised and has unit tests to ensure the output of the objects comes out as expected using the encode and decoder components.

Technically you can flood your client app with messages unless you set the pool size to a maximum but hopefully that won’t be an issue if the throughput is high enough. I guess that remains to be seen in the real world. It’s not been an issue for me yet.

osc-network

This implements a client and server implementation for the encoder and decoder components. It uses NETTY a super fast networking library for all the networking parts and is unique in that I have not seen any examples of NETTY4 using UDP to implement anything like this. This was a big sticking point in the development as I wanted to use NETTY4 but it was still in beta at the time with not many source code examples. There might be more recent version of NETTY but I’ll leave it as it is for now.

I got this component working with my Tonome project against the Puredata script that I had written but I found out that Puredata would choke when sent a lot of messages. Initially my heart sank I as I could see that I would have to write my own application to do this. But any failure is an opportunity right? My Swing skills are non-existent but my Flex skill are strong so I figured I would try to look for a Java UI library that would fit my needs. After looking around I noticed Pivot and JavaFX (which had just reached version 2 at that point). Rather than go straight in I thought I would prototype something easier (and more useful) so I embarked on trying to put a front-end on my Mavenize command line tool instead.

Pivot was eventually thrown out instead of JavaFX which I have successfully used to put a useful UI onto my application for converting any existing Java project to the Maven file structure. Once I had the skills I sat down and started to develop an application which uses the osc-common and osc-network libraries to route OSC messages to MIDI. That application is osc-router.

osc-router

oscrouterfx
The osc-router will listen to OSC messages sent to it on the chosen port and then you can assign labels to the message value received. You can’t edit the labels whilst the tool is in ‘listen’ mode but once you pause you can set them. The interface lets you route the messages (as long as they have the expected note-on and note-off values) to a specified MIDI device. It will let you save and load your settings.

This is very much a work in progress. There are possibly latency issues to do with the fact I am creating other objects to represent the incoming messages. I think there is a rewrite of the message path on the books.

The project has been put up onto GitHub here.

Lot’s of interesting JavaFX stuff to look at including file open/save dialogs,  editable label cells, combobox selection cells, icon status cells and a background service. The application also sports a message cache, MIDI device handling  and lots more. I’m also  using XStream to persist the the OSC message values and label settings to an XML file which has proved to be quite seamless.

Update #1

Update #2

The Tonome OSC-aware application is now up on Github. You can find it here. It runs on Android but there are still a few issues to iron out like the inability to edit the hostname and port values. It is here.

If you run it from the desktop it will connect to the stated hostname and port that the OSC-Router application is setup for. I have altered the router to load up the Tonome message definition by default. All you need to do it map the note on/off message and route them to a midi target.

More details in further posts.

JavaFX: Mavenize-FX – GitHub

The mavenize-fx project is now up on GitHub. This will be the new home for the project.

jfx1

I have left the google code site up to host executables only.

You can download an executable JAR file from here.

I have used the 32 bit JDK so I think it should run on any x86 based platform.

JavaFX: Mavenize-FX

As part of learning JavaFX I decided to put a nice UI onto my existing ‘mavenize’ tool. This is currently a command line tool and does what it says in tin, so to speak. To find out more about this you can read the original post here. I modified the original component to allow a ‘listener’ to attach itself to the processing section. The mavenize process calls back into the listener to notify of what it was currently doing. I wanted the UI to reflect the changes being made (not as simple as it sounds as we will discover).

What I wanted for the user interface was:

So here it is:

jfx1

I will discuss each feature in turn and what was interesting about it. Here is a rough class diagram of how the various components are connected.

mavenize_class

I have numbered the parts of the interface I want to focus on.

jfx2

Here goes…

(1) Inputs

These are the source and target directories. They are standard TextField controls and are populated from the choice of folder made using the java directory chooser. They are not populated on startup. You will need to choose a folder for each. It can’t be the same folder. If you do not populate or make them the same you will get an Alert box popping up.

jfx3

This is one thing which I was a bit non-plussed about. There doesn’t seem to be a standard Alert box for JavaFX. I ended up rolling my own from suggestions I found on StackOverflow. I have some ideas to improve my implementation to make it much nicer. I always liked the Flex alert which defocussed the whole screen and had a message in the middle. I think it would be nice to have something like that.

(2) Browse Buttons.

When these are clicked we use the folder chooser.


// At the top of the controller class.
@FXML
private TextField sourceInput;

@FXML
private Button sourceButton;

// The method.
public void sourceButtonAction(ActionEvent event)
{
	Window window = getWindow(sourceButton);

	if (window != null)
	{
		File directory = directoryChooser.showDialog(window);

		if (directory != null)
		{
			sourceInput.setText(directory.getPath());
		}
	}
}

The DirectoryChooser object is created in the constructor and shared between the source and target button handlers. Straightforward stuff.

(3) Process button.

This is an interesting one and brings in a bit of an overlap to the table view. When this button is pressed it will validate the UI inputs and if fine then it will call a method from the MavenizeClient class.

public void activateButtonAction(ActionEvent event)
{
	logger.debug("activateButtonAction");

	String sourcePath = sourceInput.getText();
	String targetPath = targetInput.getText();
	String versionText = versionInput.getText();

	if (sourcePath == null || sourcePath.isEmpty())
	{
		// Alert
		Alert alert = new Alert(stage, ApplicationMessages.MSG_ERROR_INVALID_SOURCE);

		alert.showAndWait();
	}
	else if (targetPath == null || targetPath.isEmpty())
	{
		// alert
		Alert alert = new Alert(stage, ApplicationMessages.MSG_ERROR_INVALID_TARGET);

		alert.showAndWait();
	}
	else if (versionText == null || versionText.isEmpty())
	{
		// alert
		Alert alert = new Alert(stage, ApplicationMessages.MSG_ERROR_INVALID_VERSION);

		alert.showAndWait();
	}
	else if (sourcePath.equals(targetPath))
	{
		Alert alert = new Alert(stage, ApplicationMessages.MSG_ERROR_INVALID_PATHS);

		alert.showAndWait();
	}
	else
	{
		// Process
		mavenizeClient.process(sourceInput.getText(), targetInput.getText(), versionInput.getText(), packageCombo.getValue());
	}
}

The MavenizeClient will start the background service which performs our potentially long running process.

public boolean process(String sourcePath, String targetPath, String version, String packaging)
{
	boolean status = true;

	if (sourcePath == null || sourcePath.length() == 0 || targetPath == null || targetPath.length() == 0)
	{
		throw new IllegalArgumentException();
	}
	else
	{
		mavenizeService.setSourcePath(sourcePath);
		mavenizeService.setTargetPath(targetPath);
		mavenizeService.setVersion(version);
		mavenizeService.setPackaging(packaging);

		mavenizeService.reset();

		mavenizeService.start();
	}

	return status;
}

Note the call to ‘reset’ before we call ‘start’ on the service. We need to this as each run has an internal status. Once complete we cannot call ‘start’ again without resetting the status.

The service has an ‘active’ property which we ultimately ‘bind’ the button ‘enabled’ status to so that as long as the service is running the button will be disabled. This is the power of binding and is a very elegant means to do this sort of thing.

(4) Table View.

This binding worked fine between the process button and the service active status. Not so for the TableView and the associated ObservableList. Here are the main points:

Now, I wasn’t actually expecting this to  work. Can you really update a bound list in a background thread? Well, yes so it seems..sort of.  The problem is this – the TableView does update (just to be on the safe side I used a synchronised version of the ObservableList) but it is not consistent. The TabelView was updating or not updating the list on a purely random basis. In short, we can’t expect the view to refresh itself when we are adding stuff to the bound list in a thread which is not the UI thread. It works perfectly if you fill and update the data items in the UI thread.

After a lot of head scratching I implemented a method to force the view to update. This must be kicked off using the runLater mechanism otherwise it would be modifying the scene view directly and throw a massive wobbly. It gets called whenever an item is added to the list or modified. This is a bit of a crufty solution but it works! If you examine the class diagram above you will see that the service is given a reference to the ImplementsRefresh interface.

@Override
public void refresh()
{
	Platform.runLater(new Runnable()
	{
		public void run()
		{
			ObservableList<TableColumn<ProjectResult, ?>> columns = dataTable.getColumns();
			TableColumn<ProjectResult, ?> column = columns.get(0);

			if (column != null)
			{
				column.setVisible(false);
				column.setVisible(true);
			}
		}
	});

}

Binding a list to a table in this way is a really cool feature.

(5) Icon cell.

So I am thinking that I want is a column with icons which will change according to a bound property in the data item.

Here is the data item

package com.netthreads.javafx.mavenize.model;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

/**
 * Project result bean.
 *
 */
public class ProjectResult
{
	public static final String ATTR_GROUP_ID = "groupId";
	public static final String ATTR_ARTIFACT_ID = "artifactId";
	public static final String ATTR_FILE_PATH = "filePath";
	public static final String ATTR_FILE_COUNT = "fileCount";
	public static final String ATTR_STATUS = "status";
	public static final String ATTR_WORKING = "working";

	public static final String TITLE_GROUP_ID = "groupId";
	public static final String TITLE_ARTIFACT_ID = "artifactId";
	public static final String TITLE_FILE_PATH = "File Path";
	public static final String TITLE_FILE_COUNT = "File Count";
	public static final String TITLE_STATUS = "Status";
	public static final String TITLE_WORKING = "~";

	public static final String STATUS_CREATE = "Creating";
	public static final String STATUS_COPY = "Copying";
	public static final String STATUS_FILE = "Add File";
	public static final String STATUS_POM = "Pom";

	public static final int WORKING_READY = 0;
	public static final int WORKING_BUSY = 1;
	public static final int WORKING_DONE = 2;

	private StringProperty groupIdProperty;
	private StringProperty artifactIdProperty;
	private StringProperty filePathProperty;
	private IntegerProperty fileCountProperty;
	private StringProperty statusProperty;
	private IntegerProperty workingProperty;

	/**
	 * Construct results.
	 *
	 */
	public ProjectResult()
	{
		groupIdProperty = new SimpleStringProperty(this, ATTR_GROUP_ID);
		artifactIdProperty = new SimpleStringProperty(this, ATTR_ARTIFACT_ID);
		filePathProperty = new SimpleStringProperty(this, ATTR_FILE_PATH);
		fileCountProperty = new SimpleIntegerProperty(this, ATTR_FILE_COUNT);
		statusProperty = new SimpleStringProperty(this, ATTR_STATUS);
		workingProperty = new SimpleIntegerProperty(this, ATTR_WORKING);

		groupIdProperty.set("");
		artifactIdProperty.set("");
		filePathProperty.set("");
		fileCountProperty.set(0);
		statusProperty.set("");
		workingProperty.set(WORKING_READY);
	}

	public final String getGroupId()
	{
		return groupIdProperty.get();
	}

	public final void setGroupId(String groupId)
	{
		this.groupIdProperty.set(groupId);
	}

	public final String getArtifactId()
	{
		return artifactIdProperty.get();
	}

	public final void setArtifactId(String artifactId)
	{
		this.artifactIdProperty.set(artifactId);
	}

	public final String getFilePath()
	{
		return filePathProperty.get();
	}

	public final void setFilePath(String filePath)
	{
		this.filePathProperty.set(filePath);
	}

	public final int getFileCount()
	{
		return fileCountProperty.get();
	}

	public final void setFileCount(int fileCount)
	{
		this.fileCountProperty.set(fileCount);
	}

	public String getStatus()
	{
		return statusProperty.get();
	}

	public void setStatus(String status)
	{
		this.statusProperty.set(status);
	}

	public int getWorking()
	{
		return workingProperty.get();
	}

	public void setWorking(int working)
	{
		this.workingProperty.set(working);
	}

	/**
	 * Properties.
	 *
	 */

	/**
	 * Return property.
	 *
	 * @return The property.
	 */
	public final StringProperty groupIdProperty()
	{
		return groupIdProperty;
	}

	/**
	 * Return property.
	 *
	 * @return The property.
	 */
	public StringProperty artifactIdProperty()
	{
		return groupIdProperty;
	}

	/**
	 * Return property.
	 *
	 * @return The property.
	 */
	public StringProperty filePathProperty()
	{
		return filePathProperty;
	}

	/**
	 * Return property.
	 *
	 * @return The property.
	 */
	public IntegerProperty fileCountProperty()
	{
		return fileCountProperty;
	}

	/**
	 * Return property.
	 *
	 * @return The property.
	 */
	public IntegerProperty workingProperty()
	{
		return workingProperty;
	}

}

So the table column definition looks like this:

// Working indicator
TableColumn<ProjectResult, Integer> workingCol = new TableColumn<ProjectResult, Integer>(ProjectResult.TITLE_WORKING);
workingCol.setCellValueFactory(new PropertyValueFactory<ProjectResult, Integer>(ProjectResult.ATTR_WORKING));

// Custom Cell factory converts index to image.
workingCol.setCellFactory(new Callback<TableColumn<ProjectResult, Integer>, TableCell<ProjectResult, Integer>>()
{
	@Override
	public TableCell<ProjectResult, Integer> call(TableColumn<ProjectResult, Integer> item)
	{
		WorkingTableCell cell = new WorkingTableCell();
		return cell;
	}
});

The WorkingTableCell class takes the integer value you set in the workingProperty of the ProjectResult data object and sets the appropriate icon.


package com.netthreads.javafx.mavenize.controller;

import java.io.InputStream;

import javafx.scene.control.TableCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;

import com.netthreads.javafx.mavenize.app.ApplicationStyles;
import com.netthreads.javafx.mavenize.model.ProjectResult;

/**
 * Working status custom cell.
 *
 */
public class WorkingTableCell extends TableCell<ProjectResult, Integer>
{
	private String[] ICONS =
	{
	        "/bullet_white.png", "/bullet_red.png", "/bullet_green.png"
	};

	private ImageView imageView;
	private HBox hBox;

	/**
	 * Construct cell image holder.
	 *
	 */
	public WorkingTableCell()
	{
		imageView = new ImageView();

		hBox = new HBox();
		hBox.getChildren().add(imageView);
		hBox.getStyleClass().add(ApplicationStyles.STYLE_WORKING_STATUS_CELL);
	}

	/**
	 * This will take the value and lookup the appropriate icon for display in
	 * the cell.
	 */
	@Override
	protected void updateItem(Integer item, boolean empty)
	{
		super.updateItem(item, empty);

		if (!empty)
		{
			if (item < ICONS.length)
			{
				String iconName = ICONS[item];

				InputStream stream = getClass().getResourceAsStream(iconName);
				Image goImage = new Image(stream);

				imageView.setImage(goImage);

				setGraphic(hBox);
			}
		}
	}

}

I wanted the icon image to be centred in the cell. I’m unsure if there is a programmatic way to do this but I figured I coud do it by defining a style and setting it to the holding HBox.

.workingStatusCell {
	-fx-alignment : center;
	-fx-fill-height : true;
}

And that’s it. I am going to publish the code onto either Google code or github but in the meantime here is the code. As usual Apache 2.0 licence.

Next Page →