I have a JList that is displaying a custom object (Frog) using a custom cell renderer.
frogList = new JList<Frog>();
frogModel = new DefaultListModel<Frog>();
frogList.setModel(frogModel);
frogList.setCellRenderer(new FrogBrowserCellRenderer());
//add frogs...
The frog objects contains a list of images, and I have my list pick the latest one to display. It's in a thumbnail file, so I can read it into memory and display it. However I know that the JList re-renders every time the window moves or the window needs redrawn which is really bad for performance and just not good design. The issue I have is that this list is dynamic so I cannot simply load all images at startup because users can add them at runtime and it'll auto update the list.
Some people mentioned loading the image into memory in the constructor and setting it in the getListCellRendererComponent() method but that doesn't appear to be possible because it only creates one cell renderer and uses it for everything in the list. I also verified this by printing out the constructor method. Since I will have a list of frogs with all different images this doesn't really make sense.
Here is the code I am using to create the thumbnail right now.
public Image createListThumbnail() {
try {
Image returnImg = null;
//get latest frog image
SiteImage img = frog.getLatestImage();
BufferedImage src = ImageIO.read(new File(XMLFrogDatabase.getImagesFolder()+img.getImageFileName()));
BufferedImage thumbnail = Scalr.resize(src, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_WIDTH, 200, 150, Scalr.OP_ANTIALIAS);
if (!frog.isFullySearchable()){
ImageFilter filter = new GrayFilter(true, 30);
ImageProducer producer = new FilteredImageSource(thumbnail.getSource(), filter);
returnImg = Toolkit.getDefaultToolkit().createImage(producer);
}
return returnImg;
} catch (IOException e) {
IdentiFrog.LOGGER.writeExceptionWithMessage("Unable to generate thumbnail for image (in memory).", e);
}
return null;
}
I call this method in my getListCellRendererComponent() which I know causes terrible performance but I don't understand how to cache it in memory for multiple frogs and also use only one object. Perhaps an image map? I can't seem to find any solid evidence of a proper way to do this.
Related
I'm not really sure if i have understood the principe of the DataFlavors correctly, but how can i actually set the DataFlavor(s) of a JComponent?
Every time i call the getDataFlavor method the output is this:
java.awt.datatransfer.DataFlavor[mimetype=application/x-java-file-list;representationclass=java.util.List]
My problem is, that i want to drag images from the desktop or any other place right into my JPanel. It's working through the DataFlavor.javaFileListFlavor, but is there no way that i can create a custom flavor which only accepts PNG and JPG Files for example?
(I know that it is actually possible to create Custom Flavors but i have no clue how i can "enable" the new created flavors for my components)
Or is there a way to ensure whether the dragged in Item is a PNG or JPG with the javaFileListFlavor?
I hope that i could explain my question well enough (I'm not a master of this language, but i'm trying my best ;) )
Or is there a way to ensure whether the dragged in Item is a PNG or JPG with the javaFileListFlavor?
Take a look at the Swing tutorial on Top Level Drop. It shows how to drag a file from the desktop to a JTextArea.
Take a look at the canImport(...) and importData(...) methods of the TransferHandler. The canImport(...) method currently only checks that you have a FileListFlavor. So you would need to add extra logic to see the actual File is a PNG or JPG.
If you look at the importData(...) method you can see how to get the File object from the TransferSupport object so you can implement the above check.
Edit:
but as described the Exception pops up
I just ignore the Exception. Here is the modified code for the tutorial that only allows your to copy ".java" files into the text area.
private TransferHandler handler = new TransferHandler() {
public boolean canImport(TransferHandler.TransferSupport support) {
if (!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
return false;
}
Transferable t = support.getTransferable();
try
{
java.util.List<File> l = (java.util.List<File>)t.getTransferData(DataFlavor.javaFileListFlavor);
File file = l.get(0);
String fileName = file.getName();
if (!file.getName().endsWith(".java"))
return false;
}
catch (Exception e)
{
// ignore
}
if (copyItem.isSelected()) {
boolean copySupported = (COPY & support.getSourceDropActions()) == COPY;
if (!copySupported) {
return false;
}
support.setDropAction(COPY);
}
return true;
}
Works fine for me using JDK8 on Winodow 7.
I am using the JavaFX Combobox for the first time... and I have over 2000 icons in the combobox (they can be filtered via AutoCompleteComboBoxListener that I found from StackOverflow).
I am planning to use the ExecutorService to fetch the images from a zip-file.
Now, the problem is that how can I figure out the currently visible items in the Combobox?
I am setting a custom ListCellFactory for the ComboBox, and I have a custom ListCell class, that also displays the icon. Can I somehow check from within the ListCell object whether the item "is showing" ?
Thanks.
Just note first that if you are loading the images from individual files instead of from a zip file, there is a mechanism that avoids having to work directly with any kind of threading at all:
ComboBox<MyDataType> comboBox = new ComboBox<>();
comboBox.setCellFactory(listView -> new ListCell<MyDataType>() {
private ImageView imageView = new ImageView();
#Override
public void updateItem(MyDataType item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
String imageURL = item.getImageURL();
Image image = new Image(imageURL, true); // true means load in background
imageView.setImage(image);
setGraphic(imageView);
}
}
});
Unfortunately, if you're loading from a zip file, I don't think you can use this, so you'll need to create your own background tasks. You need to just be a little careful to make sure that you don't use an image loaded in the background if the item in the cell changes during the loading process (which is pretty likely if the user scrolls a lot).
(Update note: I changed this to listen for changes in the itemProperty() of the cell, instead of using the updateItem(...) method. The updateItem(...) method can be called more frequently than when the actual item displayed by the cell changes, so this approach avoids unnecessary "reloading" of images.)
Something like:
ExecutorService exec = ... ;
ComboBox<MyDataType> comboBox = new ComboBox<>();
comboBox.setCellFactory(listView -> {
ListCell<MyDataType> cell = new ListCell<MyDataType>() ;
ImageView imageView = new ImageView();
ObjectProperty<Task<Image>> loadingTask = new SimpleObjectProperty<>();
cell.emptyProperty().addListener((obs, wasEmpty, isNotEmpty) -> {
if (isNowEmpty) {
cell.setGraphic(null);
cell.setText(null);
} else {
cell.setGraphic(imageView);
}
});
cell.itemProperty().addListener((obs, oldItem, newItem) -> {
if (loadingTask.get() != null &&
loadingTask.get().getState() != Worker.State.SUCCEEDED &&
loadingTask.get().getState() != Worker.State.FAILED) {
loadingTask.get().cancel();
}
loadingTask.set(null) ;
if (newItem != null) {
Task<Image> task = new Task<Image>() {
#Override
public Image call() throws Exception {
Image image = ... ; // retrieve image for item
return image ;
}
};
loadingTask.set(task);
task.setOnSucceeded(event -> imageView.setImage(task.getValue()));
exec.submit(task);
cell.setText(...); // some text from item...
}
});
return cell ;
});
Some thoughts on performance here:
First, the "virtualized" mechanism of the ComboBox means that only a small number of these cells will ever be created, so you don't need to worry that you're immediately starting thousands of threads loading images, or indeed that you will ever have thousands of images in memory.
When the user scrolls through the list, the itemProperty(...) may change quite frequently as the cells are reused for new items. It's important to make sure you don't use images from threads that are started but don't finish before the item changes again; that's the purpose of canceling an existing task at the beginning of the item change listener. Canceling the task will prevent the onSucceeded handler from being invoked. However, you will still have those threads running, so if possible the implementation of your call() method should check the isCancelled() flag and abort as quickly as possible if it returns true. This might be tricky to implement: I would experiment and see how it works with a simple implementation first.
Even if your list has 2000 items javafx will only create listcell objects for the visible cells (plus one or two more for half visible cells) so there's not really a lot TODO for you to load Images lazy - just load them when updateItem is called - and maybe cache already loaded Images in a lifo Cache so that not all of them stay in memory
Current visible item implies the current selected item on combobox. You can get the selected item using
comboboxname.getSelectionModel().getSelectedItem();
I'm having troubles making a second call to load a Google Map in a GWT app. The problem itself is that once the map is called, it won't fit the container size. This is a usual problem, as depicted in many previous SO questions:
Here
Here
Here
Here
Let me state that I've tried all of the above and sadly nothing seems to work. I must also say I'm using the unofficial version of GWT Maps API v3, which can be found here. Thus, this is the problem:
Now, weird enough, if I change the browser size, map displays correctly:
Thus, it looks like I need to "dispatch" the onResize event somehow...but I tried with all of the above methods and nothing seemed to work. Just for clarification this, is the part where I construct the map and add it to the container:
private void buildMapMarinesPark() {
//Visualizar datos...
LatLng center = LatLng.newInstance(52.62715,1.7734);
MapOptions opts = MapOptions.newInstance();
opts.setZoom(9);
opts.setCenter(center);
opts.setMapTypeId(MapTypeId.HYBRID);
MapTypeControlOptions controlOptions = MapTypeControlOptions.newInstance();
controlOptions.setMapTypeIds(MapTypeId.values()); // use all of them
controlOptions.setPosition(ControlPosition.TOP_RIGHT);
opts.setMapTypeControlOptions(controlOptions);
mapMarinePark = new MapWidget(opts);
mapMarinePark.setSize("100%", "100%");
// Add some controls for the zoom level
List<EuropeanMarineParkDataEntity> parksPerAnio = null;
listPolygon = new ArrayList<Polygon>();
Polygon poly = null;
for(int i=2003;i<=ANIO_MAP_PARK;i++){
parksPerAnio = this.hashAnioParks.get(""+i);
if(parksPerAnio != null){
for(EuropeanMarineParkDataEntity emp : parksPerAnio){
poly = this.createPolygon(emp);
poly.setMap(mapMarinePark);
listPolygon.add(poly);
}
}
}
((Element)DOM.getElementById("currentYear")).setPropertyObject("innerHTML", ""+(ANIO_MAP_PARK));
// Add the map to the HTML host page
final DockLayoutPanel dock = new DockLayoutPanel(Unit.PX);
dock.addNorth(mapMarinePark, 500);
RootPanel.get("mapContainerProfile2").add(dock);
RootPanel.get("timeline").setVisible(true);
RootPanel.get("mapPanel2").setVisible(true);
RootPanel.get("gadget_marinepark").setVisible(true);
mapMarinePark.triggerResize(); --> Does not work!
onLoadedMapMarinePark();
}
I guess you try to draw the map when the DOM hasn't been fully constructed and the wrong dimensions are retrieved.
Try to draw/create the map in a callback of a
Scheduler.get().scheduleDeferred() call.
Update:
Also you are mixing a DockLayoutPanel with a RootPanel.
That will cause issues. Use a RootLayoutPanel instead.
Construct the DOM normally and at the point where you normally add your map to the DockLayoutPanel call scheduleDeferred() and add the map to the panel in the callback
I'm creating a small game in android. Anything that needs to be drawn on screen extends my VisibleObject class, I then have a Manager class that i can add all my visible game objects to and each frame i tell the manager to call the draw function of everything within it.
Here's were objects are initialised and added to the manager:
#Override
public void surfaceCreated(SurfaceHolder holder) {
fps = new TextDrawable(Color.BLACK, 14, 100, 40, 40);
player = new Player();
map = new Map();
leftTouch = new TouchCircle(Color.GRAY);
manager.add("fpsText", fps);
manager.add("leftTouch", leftTouch);
manager.add("player", player);
manager.add("map", map);
gameloop = new GameLoop(getHolder(), this, fps);
gameloop.setRunning(true);
gameloop.start();
}
Now the problem i'm having is with the draw order, if you look at the order the objects are added to the manager for reference..
I can tell you for certain that the player is being drawn on top of
the map! These are both drawn by drawing there respective bitmaps with drawBitmap(..).
However the fpsText and leftTouch are being drawn underneath the map! These are drawn using drawText(..) and drawOval(..) respectively.
Even though they implement different Canvas.draw.. functions, I would expect them all to be drawn in order as I just pass the canvas object i have to my manager class and then let the manager cycle through each object passing it that canvas to draw with.
Can anyone clear up for me why bitmaps seem to be drawn on top and what the solution should be to get my fps and touch area drawn above the player and map bitmaps? I'd Appreciate it.
EDIT: I am using private ConcurrentSkipListMap<String, VisibleObject> objectMap; within my manager to store the objects and drawing like so..
public void draw(Canvas c){
for (Map.Entry<String, VisibleObject> object : objectMap.entrySet()){
synchronized(object){
object.getValue().draw(c);
}
}
}
Bitmaps are not drawn on top of the text unless you draw Bitmap after you drawn text (and position overlaps).
You haven't disclose onDraw method so I can't be sure, but I suspect that you are not calling drawing methods in right order.
How does you manager stores the values added to it? Maybe you use Map implementation that doesn't maintain order of elements added (most implementations don't, LinkedHashmap does).
I'm working on a SWT/Jface project based on Java SE, i want to move the image of a TitleAreaDialog to the left. is it possible ? if not is there anyway ?
Thanks,
There is no way to configure it using API, the layout is hard-coded. One way is to hack into the dialog controls and change their layout data, but it is likely easier to implement your own class (using TitleAreaDialog as an example).
If you subclass TitleAreaDialog you have to override createContents(Composite) method, otherwise the TitleAreaDialog will create its own title area by calling createTitleArea(). I suggest that at first you just copy the code from TitleAreaDialog.createContents() and start replacing stuff that you need to be done differently. I don't know exactly what needs to be done without actually doing everything.
You can modify the layout data of the image label as follows:
TitleAreaDialog tad = new TitleAreaDialog(getShell()) {
#Override
protected Control createContents(Composite parent) {
Control control = super.createContents(parent);
Label label = getTitleImageLabel();
FormData data = (FormData) label.getLayoutData();
data.left = new FormAttachment(0, 0);
data.right = null;
return control;
}
};
tad.setTitle("title");
tad.setTitleImage(Activator.imageDescriptorFromPlugin(
Activator.PLUGIN_ID, "image.gif").createImage());
tad.open();