Implement undo/redo button in JavaFX [closed] - class

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 months ago.
Improve this question
Ive been scrolling and scrolling and i dont seem to find any solution. I am making a simple java fx program where i draw different shapes. The problem is that i dont know how to implement an undo button. I have found that you can use stacks, command pattern, arraydeque and so on... But i cant fix it..
Here is some of the code.
public class shapeModel {
//private final List<Shape> undoHistory = new ArrayList<>();
//private final Deque<Deque<Shape>> redoHistory;
private final ObservableList<Shape> shapeList;
private final ObjectProperty<Color> colorPickerSelect;
private final int historyIndex = -1;
private ShapeType currentShape;
public shapeModel() {
//this.redoHistory = new ArrayDeque<>();
this.colorPickerSelect = new SimpleObjectProperty<>(Color.GREEN);
this.shapeList = FXCollections.observableArrayList(shape -> new Observable[]{
shape.colorProperty()
});
}
public ShapeType getCurrentShape() {
return currentShape;
}
public void setCurrentShape(ShapeType currentShape) {
this.currentShape = currentShape;
}
public void addShapes(Shape shape) {
if (!(shape == null))
this.shapeList.add(shape);
}
public Color getColorPickerSelect() {
return colorPickerSelect.get();
}
public ObjectProperty<Color> colorPickerSelectProperty() {
return colorPickerSelect;
}
public ObservableList<Shape> getShapeObservableList() {
return shapeList;
}
public void undo() {
}
}
Controller class:
public class HelloController {
public Button eraserButton;
public Button undoButton;
public BorderPane scenePane;
public ColorPicker myColorPicker;
public ChoiceBox<String> myChoiceBox;
public GraphicsContext context;
public Canvas canvas;
public shapeModel model;
public void initialize() {
context = canvas.getGraphicsContext2D();
model.getShapeObservableList().addListener((ListChangeListener<Shape>) e -> listChanged());
myColorPicker.valueProperty().bindBidirectional(model.colorPickerSelectProperty());
}
public HelloController() {
this.model = new shapeModel();
}
//
// private int count = 0;
//
// private final Shape[] shapes = new Shape[30];
public void canvasClicked(MouseEvent mouseEvent) {
Shape shape = Shape.creatingShapes(model.getCurrentShape(),mouseEvent.getX(),mouseEvent.getY(),50,50,myColorPicker.getValue());
model.addShapes(shape);
// redraw();
// shapes[count] = shape;
// count++;
// paintCanvas();
}
public void listChanged() {
var context = canvas.getGraphicsContext2D();
model.getShapeObservableList().forEach(s -> s.draw(context));
}
public void undoClicked() {
}
// public void redo() {
//
// if(historyIndex < model.getShapeObservableList().size()-1) {
// historyIndex++;
// model.getShapeObservableList(),(historyIndex).
// }
//
// }
public void handleClick() {
FileChooser chooser = new FileChooser();
File file = chooser.showSaveDialog(scenePane.getScene().getWindow());
}
public void onCircleClicked(ActionEvent e) {
model.setCurrentShape(ShapeType.CIRCLE);
}
public void onRectangleClicked(ActionEvent e) {
model.setCurrentShape(ShapeType.RECTANGLE);
}
public void exitAction(ActionEvent actionEvent) {
Platform.exit();
}
public void eraser() {
context.setFill(Color.WHITE);
context.fillRect(0,0,canvas.getWidth(), canvas.getHeight());
// context.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
}
}

Maybe this can help. This is my first time attempting something like this. The experts may have a better, more efficient way of doing this.
My idea was to keep up with a List of WritableImages as you draw. Use that list to go forward and backward through history.
Main
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundImage;
import javafx.scene.layout.BackgroundPosition;
import javafx.scene.layout.BackgroundRepeat;
import javafx.scene.layout.BackgroundSize;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
/**
* Code from: #web http://java-buddy.blogspot.com/
*/
public class App extends Application {
Canvas canvas = new Canvas(400, 400);
StackPane spCanvasHolder = new StackPane(canvas);
List<WritableImage> history = new ArrayList();
AtomicInteger currentlyDisplaying = new AtomicInteger();
#Override
public void start(Stage primaryStage) {
final GraphicsContext graphicsContext = canvas.getGraphicsContext2D();
initDraw(graphicsContext);
canvas.addEventHandler(MouseEvent.MOUSE_PRESSED, (MouseEvent event) ->
{
graphicsContext.beginPath();
graphicsContext.moveTo(event.getX(), event.getY());
graphicsContext.stroke();
history.add(spCanvasHolder.snapshot(new SnapshotParameters(), null));
System.out.println("Current Display: " + currentlyDisplaying.incrementAndGet());
});
canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED, (MouseEvent event) ->
{
graphicsContext.lineTo(event.getX(), event.getY());
graphicsContext.stroke();
history.add(spCanvasHolder.snapshot(new SnapshotParameters(), null));
System.out.println("Current Display: " + currentlyDisplaying.incrementAndGet());
});
canvas.addEventHandler(MouseEvent.MOUSE_RELEASED, (MouseEvent event) ->
{
System.out.println("History size: " + history.size());
});
Button btnForwardHistory = new Button(">");
btnForwardHistory.setOnAction((t) ->
{
if(currentlyDisplaying.get() < history.size())
{
File file = new File("CanvasTemp.png");
try
{
canvas.getGraphicsContext2D().clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
ImageIO.write(SwingFXUtils.fromFXImage(history.get(currentlyDisplaying.incrementAndGet()), null), "png", file);
BackgroundImage bI = new BackgroundImage(new Image(new FileInputStream(file)), BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, BackgroundSize.DEFAULT);
spCanvasHolder.setBackground(new Background(bI));
}
catch (Exception s) {
System.out.println(s.toString());
}
}
});
Button btnBackwardHistory = new Button("<");
btnBackwardHistory.setOnAction((t) ->
{
System.out.println("Current Display: " + currentlyDisplaying.get());
if(currentlyDisplaying.get() > 0)
{
File file = new File("CanvasTemp.png");
try
{
canvas.getGraphicsContext2D().clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
ImageIO.write(SwingFXUtils.fromFXImage(history.get(currentlyDisplaying.decrementAndGet()), null), "png", file);
BackgroundImage bI = new BackgroundImage(new Image(new FileInputStream(file)), BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, BackgroundSize.DEFAULT);
spCanvasHolder.setBackground(new Background(bI));
}
catch (Exception s) {
System.out.println(s.toString());
}
}
});
VBox vbHistoryControls = new VBox(btnBackwardHistory, btnForwardHistory);
StackPane root = new StackPane();
root.getChildren().add(new HBox(vbHistoryControls, spCanvasHolder));
Scene scene = new Scene(root);
primaryStage.setTitle("java-buddy.blogspot.com");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private void initDraw(GraphicsContext gc){
double canvasWidth = gc.getCanvas().getWidth();
double canvasHeight = gc.getCanvas().getHeight();
gc.setFill(Color.LIGHTGRAY);
gc.setStroke(Color.BLACK);
gc.setLineWidth(5);
gc.fill();
gc.strokeRect(
0, //x of the upper left corner
0, //y of the upper left corner
canvasWidth, //width of the rectangle
canvasHeight); //height of the rectangle
gc.setFill(Color.RED);
gc.setStroke(Color.BLUE);
gc.setLineWidth(1);
}
}
module-info
module com.mycompany.javafxsimpletest {
requires javafx.controls;
requires javafx.swing;
exports com.mycompany.javafxsimpletest;
}
Output
I would not use this code in a production app, but maybe the ideas can get you in the right direction.

This example is based on the UndoFX framework:
https://github.com/FXMisc/UndoFX
I offer up the example without a lot of explanation. You could study and learn from it or ignore it. It isn't going to do exactly what you want, but there might be some useful ideas from it for you.
The example:
Uses the scene graph instead of a Canvas.
Only adds shapes to a pane.
Provides the ability to remove the added shapes and add them back again.
The example is probably overcomplicated for the limited functionality it provides but might be structured in such a way that it is more easily adaptable to adding additional functionality.
The example does not demonstrate manipulating shapes (such as selecting them, changing their border and fill colors, changing their sizes, etc) and undoing or redoing such changes. But if you wanted to extend functionality for that, you could use the app as a basis and look into the UndoFX demo app which shows how to apply undo and redo to multiple properties on a shape. Depending on the functionality added, this could be quite a difficult task as generic undo/redo across a wide range of functionality is inherently difficult (IMO).
com/example/shapechanger/ShapeChangerApp.java
package com.example.shapechanger;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
import javafx.stage.Stage;
import org.fxmisc.undo.UndoManager;
import org.fxmisc.undo.UndoManagerFactory;
import org.reactfx.EventSource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class ShapeChangerApp extends Application {
#Override
public void start(Stage stage) {
Model model = new Model();
EventSource<ShapeChange> changes = new EventSource<>();
UndoManager<ShapeChange> undoManager = new UndoProvider(changes).getUndoManager();
Ribbon ribbon = new Ribbon(model, undoManager);
DrawingPane drawingPane = new DrawingPane(model, changes);
ScrollPane scrollPane = new ScrollPane(drawingPane);
scrollPane.setPrefSize(300, 300);
VBox layout = new VBox(ribbon, scrollPane);
VBox.setVgrow(scrollPane, Priority.ALWAYS);
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
class DrawingPane extends Pane {
private static final double DRAWING_PANE_SIZE = 1_000;
private final Model model;
private final EventSource<ShapeChange> changes;
private final ShapeFactory shapeFactory = new ShapeFactory();
public DrawingPane(Model model, EventSource<ShapeChange> changes) {
this.model = model;
this.changes = changes;
setOnMouseClicked(e -> addShape(e.getX(), e.getY()));
setMinSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
setPrefSize(DRAWING_PANE_SIZE, DRAWING_PANE_SIZE);
setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
}
private void addShape(double x, double y) {
DrawableShape drawableShape = model.selectedShapeProperty().get();
Shape shape = shapeFactory.create(
drawableShape, x, y
);
ShapeChange shapeChange = new ShapeChange(
changes, this, shape, true
);
shapeChange.apply();
}
}
class Model {
private final ObjectProperty<DrawableShape> selectedShape = new SimpleObjectProperty<>();
public ObjectProperty<DrawableShape> selectedShapeProperty() {
return selectedShape;
}
}
class Ribbon extends ToolBar {
public Ribbon(Model model, UndoManager<ShapeChange> undoManager) {
UndoControls undoControls = new UndoControls(undoManager);
ShapeSelector shapeSelector = new ShapeSelector();
model.selectedShapeProperty().bindBidirectional(
shapeSelector.selectedShapeProperty()
);
getItems().addAll(undoControls.getControls());
getItems().add(createSpacer());
getItems().addAll(shapeSelector.getControls());
}
private Node createSpacer() {
Pane spacer = new Pane();
spacer.setPrefWidth(10);
return spacer;
}
}
class UndoProvider {
private final UndoManager<ShapeChange> undoManager;
public UndoProvider(EventSource<ShapeChange> changes) {
undoManager = UndoManagerFactory.unlimitedHistorySingleChangeUM(
changes,
ShapeChange::invert,
ShapeChange::apply
);
}
public UndoManager<ShapeChange> getUndoManager() {
return undoManager;
}
}
class ShapeChange {
private final EventSource<ShapeChange> changes;
private final DrawingPane drawingPane;
private final Shape shape;
private final boolean adding;
public ShapeChange(EventSource<ShapeChange> changes, DrawingPane drawingPane, Shape shape, boolean adding) {
this.changes = changes;
this.drawingPane = drawingPane;
this.shape = shape;
this.adding = adding;
}
public void apply() {
if (adding) {
drawingPane.getChildren().add(shape);
} else {
drawingPane.getChildren().remove(shape);
}
changes.push(this);
}
public ShapeChange invert() {
return new ShapeChange(
this.changes,
this.drawingPane,
this.shape,
!adding
);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ShapeChange that = (ShapeChange) o;
return adding == that.adding && Objects.equals(changes, that.changes) && Objects.equals(drawingPane, that.drawingPane) && Objects.equals(shape, that.shape);
}
#Override
public int hashCode() {
return Objects.hash(changes, drawingPane, shape, adding);
}
}
enum DrawableShape {
SQUARE, CIRCLE
}
class UndoControls {
private final Button undo = new Button("<");
private final Button redo = new Button(">");
public UndoControls(UndoManager<ShapeChange> undoManager) {
undo.disableProperty().bind(undoManager.undoAvailableProperty().map(x -> !x));
redo.disableProperty().bind(undoManager.redoAvailableProperty().map(x -> !x));
undo.setOnAction(evt -> undoManager.undo());
redo.setOnAction(evt -> undoManager.redo());
}
public List<Node> getControls() {
return List.of(undo, redo);
}
}
class ShapeSelector {
private final ObjectProperty<DrawableShape> selectedShape = new SimpleObjectProperty<>(DrawableShape.SQUARE);
private final RadioButton squareSelector = looksLikeToggleButton(new RadioButton());
private final RadioButton circleSelector = looksLikeToggleButton(new RadioButton());
public ShapeSelector() {
ShapeFactory shapeFactory = new ShapeFactory();
Rectangle square = shapeFactory.createSquare(0, 0, Color.LIGHTGRAY);
Circle circle = shapeFactory.createCircle(0, 0, Color.LIGHTGRAY);
ToggleGroup toggleGroup = new ToggleGroup();
squareSelector.setGraphic(square);
squareSelector.setToggleGroup(toggleGroup);
squareSelector.setSelected(true);
circleSelector.setGraphic(circle);
circleSelector.setToggleGroup(toggleGroup);
Map<Toggle,DrawableShape> shapeMap = Map.of(
squareSelector, DrawableShape.SQUARE,
circleSelector, DrawableShape.CIRCLE
);
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) ->
selectedShape.set(
shapeMap.get(newValue)
)
);
}
public ObjectProperty<DrawableShape> selectedShapeProperty() {
return selectedShape;
}
public List<Node> getControls() {
return List.of(squareSelector, circleSelector);
}
private RadioButton looksLikeToggleButton(RadioButton radioButton) {
radioButton.getStyleClass().remove("radio-button");
radioButton.getStyleClass().add("toggle-button");
return radioButton;
}
}
class ShapeFactory {
private static final double BASE_SIZE = 20;
private static final double STROKE_WIDTH = 3;
private static final List<Color> palette = List.of(
Color.LIGHTBLUE,
Color.LIGHTGREEN,
Color.LIGHTCORAL,
Color.LIGHTGOLDENRODYELLOW,
Color.LIGHTPINK,
Color.LIGHTSALMON,
Color.LIGHTSEAGREEN,
Color.LIGHTYELLOW
);
private int colorIdx = 0;
public Rectangle createSquare(double x, double y, Color color) {
Rectangle square = new Rectangle(BASE_SIZE, BASE_SIZE);
square.setFill(color);
square.setStroke(color.darker());
square.setStrokeWidth(STROKE_WIDTH);
square.relocate(x, y);
return square;
}
public Circle createCircle(double x, double y, Color color) {
Circle circle = new Circle(BASE_SIZE / 2);
circle.setFill(color);
circle.setStroke(color.darker());
circle.setStrokeWidth(STROKE_WIDTH);
circle.setCenterX(x);
circle.setCenterY(y);
return circle;
}
public Shape create(DrawableShape drawableShape, double x, double y) {
return switch (drawableShape) {
case SQUARE -> createSquare(x, y, nextColor());
case CIRCLE -> createCircle(x, y, nextColor());
};
}
private Color nextColor() {
Color chosenColor = palette.get(colorIdx);
colorIdx = (colorIdx + 1) % palette.size();
return chosenColor;
}
}
module-info.java
module com.example.shapechanger {
requires javafx.controls;
requires org.fxmisc.undo;
requires reactfx;
exports com.example.shapechanger;
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>ShapeChanger</artifactId>
<version>1.0-SNAPSHOT</version>
<name>ShapeChanger</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>19</version>
</dependency>
<dependency>
<groupId>org.fxmisc.undo</groupId>
<artifactId>undofx</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>19</source>
<target>19</target>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>

Related

Eclipse OverviewRuler not showing annotations

I created a SourceViewer as described in this link https://wiki.eclipse.org/Platform_Text
I could not get the annotation indications on the right side of the editor.
I am trying out this in a view instead of an editor.
I tried calling overViewRuler.addAnnotationType("My annotation type"); and also called the overViewRuler.update(); Nothing seems to work. Is there any way to show the marks on the right side overview ruler of a source viewer in a view?
I have found the answer.
The problem was
overviewRulerPreferenceValue="true"
was missing in the plugin.xml. I am giving the complete code below in case any one wants to try.
package sourceviewsample;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.AnnotationModel;
import org.eclipse.jface.text.source.CompositeRuler;
import org.eclipse.jface.text.source.IAnnotationAccess;
import org.eclipse.jface.text.source.IOverviewRuler;
import org.eclipse.jface.text.source.ISharedTextColors;
import org.eclipse.jface.text.source.LineNumberRulerColumn;
import org.eclipse.jface.text.source.OverviewRuler;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.internal.editors.text.EditorsPlugin;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.texteditor.AnnotationPreference;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
/**
* An example of using a SourceViewer with Annotation support outside of the
* TextEditor class. This annotations can be configured in the preferences if
* the markerAnnotationSpecification is setup in the plugin.xml.
*
* To execute this, run as an Eclipse Application and then open a file using
* Open with.. -> Other, and select Sample Editor. You will see the text that
* comes in this example and the highlight.
*/
public class SampleEditor extends ViewPart
{
public static final String ANNO_TYPE = "com.mycompany.element";
public static final String ANNO_KEY_HIGHLIGHT = "annotateElemHighlight";
public static final String ANNO_KEY_OVERVIEW = "annotateElemOverviewRuler";
public static final String ANNO_KEY_VERTICAL = "annotateElemVertialRuler";
public static final String ANNO_KEY_TEXT = "annotateElemText";
public static final String ANNO_KEY_COLOR = "annotateElemColor";
protected SourceViewer _sourceViewer;
protected SourceViewerDecorationSupport _sds;
protected IDocument _document;
protected AnnotationModel _annotationModel;
protected String _docString = "this\nis\na\ntest\ndocument";
public SampleEditor()
{
super();
}
public void createPartControl(Composite parent)
{
int VERTICAL_RULER_WIDTH = 12;
int styles = SWT.V_SCROLL
| SWT.H_SCROLL
| SWT.MULTI
| SWT.BORDER
| SWT.FULL_SELECTION;
ISharedTextColors sharedColors = EditorsPlugin.getDefault()
.getSharedTextColors();
IAnnotationAccess iaccess = new IAnnotationAccess() {
#Override
public boolean isTemporary(Annotation annotation) {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean isMultiLine(Annotation annotation) {
// TODO Auto-generated method stub
return false;
}
#Override
public Object getType(Annotation annotation) {
// TODO Auto-generated method stub
return ANNO_TYPE;
}
};
IOverviewRuler overviewRuler = new OverviewRuler(iaccess,
VERTICAL_RULER_WIDTH,
sharedColors);
CompositeRuler ruler = new CompositeRuler(VERTICAL_RULER_WIDTH);
_document = new Document();
_document.set(_docString);
_annotationModel = new AnnotationModel();
_annotationModel.connect(_document);
_sourceViewer = new SourceViewer(parent,
ruler,
overviewRuler,
true,
styles);
_sourceViewer.configure(new SourceViewerConfiguration());
_sds = new SourceViewerDecorationSupport(_sourceViewer,
overviewRuler,
null,
sharedColors);
AnnotationPreference ap = new AnnotationPreference();
ap.setColorPreferenceKey(ANNO_KEY_COLOR);
ap.setColorPreferenceValue(new RGB(150, 200, 250));
ap.setHighlightPreferenceKey(ANNO_KEY_HIGHLIGHT);
ap.setVerticalRulerPreferenceKey(ANNO_KEY_VERTICAL);
ap.setOverviewRulerPreferenceKey(ANNO_KEY_OVERVIEW);
ap.setOverviewRulerPreferenceValue(true);
ap.setTextPreferenceKey(ANNO_KEY_TEXT);
ap.setAnnotationType(ANNO_TYPE);
_sds.setAnnotationPreference(ap);
_sds.install(EditorsPlugin.getDefault().getPreferenceStore());
_sourceViewer.setDocument(_document, _annotationModel);
_sourceViewer.getControl().setLayoutData(new GridData(SWT.FILL,
SWT.FILL,
true,
true));
ruler.addDecorator(0, new LineNumberRulerColumn());
overviewRuler.addAnnotationType(ANNO_TYPE);
overviewRuler.getControl().setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
Annotation annotation = new Annotation(false);
annotation.setType(ANNO_TYPE);
Position position = new Position(0, 4);
_annotationModel.addAnnotation(annotation, position);
try {
_document.addPosition(position);
} catch (BadLocationException e) {
e.printStackTrace();
}
overviewRuler.setModel(_annotationModel);
}
public void dispose()
{
// The _sourceViewer goes away automatically when the editor goes
// away because it's hooked to the controls
_sds.dispose();
}
//
// This stuff below is just needed to make the EditorPart happy
//
public void doSave(IProgressMonitor monitor)
{
}
public void doSaveAs()
{
}
// public void init(IEditorSite site, IEditorInput input)
// throws PartInitException
// {
// setSite(site);
// setInput(input);
// }
//
// public boolean isDirty()
// {
// return false;
// }
public boolean isSaveAsAllowed()
{
return false;
}
public void setFocus()
{
}
}
Here is the plugin.xml as well
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension
point="org.eclipse.ui.editors.annotationTypes">
<type
markerSeverity="0"
name="com.mycompany.element">
</type>
</extension>
<extension
point="org.eclipse.ui.editors.markerAnnotationSpecification">
<specification
annotationType="com.mycompany.element"
colorPreferenceKey="annotateElemColor"
colorPreferenceValue="255,255,0"
highlightPreferenceKey="annotateElemHighlight"
highlightPreferenceValue="true"
includeOnPreferencePage="true"
label="Sample Annotation"
overviewRulerPreferenceKey="annotateElemOverviewRuler"
overviewRulerPreferenceValue="true"
textPreferenceKey="annotateElemText"
verticalRulerPreferenceKey="annotateElemVerticalRuler"
verticalRulerPreferenceValue="true">
</specification>
</extension>
<extension
point="org.eclipse.ui.views">
<view id="sourceviewsample.SampleEditor"
name="Source Viewer"
class="sourceviewsample.SampleEditor"/>
</extension>
</plugin>

Updating the color of rows of a TableView consumes too much CPU

I am making an application that receives alerts.
An alert can have 4 possible states:
Unresolved_New_0
Unresolved_New_1
Unresolved_Old
Resolved
When an alert is received, it is in Unresolved_New_0 state. For 10 seconds, every 0.5s the state changes from Unresolved_New_0 to Unresolved_New_1 and vice-versa. Depending on state I, set a different background color to the table row (so that it flashes, for 10s).
When the 10s pass, the alert transitions to Unresolved_Old state. This causes its color to stop changing.
To implement this, I have a ScheduledThreadPoolExecutor that I use to submit an implementation of Runnable that for some time executes a runnable using Platform.runLater.
static class FxTask extends Runnable {
/**
*
* #param runnableDuring Runnable to be run while the task is active (run on the JavaFX application thread).
* #param runnableAfter Runnable to be run after the given duration is elapsed (run on the JavaFX application thread).
* #param duration Duration to run this task for.
* #param unit Time unit.
*/
public static FxTask create(final Runnable runnableDuring, final Runnable runnableAfter, final long duration, final TimeUnit unit) {
return new FxTask(runnableDuring, runnableAfter, duration, unit);
}
#Override
public void run() {
if (System.nanoTime() - mTimeStarted >= mTimeUnit.toNanos(mDuration) )
{
cancel();
Platform.runLater(mRunnableAfter);
}
else
Platform.runLater(mRunnableDuring);
}
private FxTask(final Runnable during, final Runnable after, final long duration, final TimeUnit unit) {
mRunnableDuring = during;
mRunnableAfter = after;
mDuration = duration;
mTimeUnit = unit;
mTimeStarted = System.nanoTime();
}
private final Runnable mRunnableDuring;
private final Runnable mRunnableAfter;
private final long mDuration;
private final TimeUnit mTimeUnit;
private final long mTimeStarted;
}
And I schedule Alerts using that Runnable as follows:
final Alert alert = new Alert(...);
scheduler.scheduleAtFixedRate(FxTask.create(
() -> {
switch (alert.alertStateProperty().get()) {
case UNRESOLVED_NEW_0:
alert.alertStateProperty().set(Alert.State.UNRESOLVED_NEW_1);
refreshTable(mAlertsTable);
break;
case UNRESOLVED_NEW_1:
alert.alertStateProperty().set(Alert.State.UNRESOLVED_NEW_0);
refreshTable(mAlertsTable);
break;
}
},
() -> { // This is run at the end
if (equalsAny(alert.alertStateProperty().get(), Alert.State.UNRESOLVED_NEW_0, SpreadAlert.State.UNRESOLVED_NEW_1)) {
alert.alertStateProperty().set(Alert.State.UNRESOLVED_OLD);
refreshTable(mAlertsTable);
}
},
10, TimeUnit.SECONDS), 0, 500, TimeUnit.MILLISECONDS
);
Note: alertStateProperty() is not shown on the TableView (it is not bound to any of its columns).
So in order to force JavaFx to redraw, I have to use refreshTable(), which unfortunately redraws the whole table (?).
public static <T> void refreshTable(final TableView<T> table) {
table.getColumns().get(0).setVisible(false);
table.getColumns().get(0).setVisible(true);
}
The problem is that even if I create a small number of Alerts at the same time, CPU usage goes very high: from 20% to 84% sometimes, averaging at about 40%. When the 10s pass for all alerts, CPU consumptions returns to 0%. If I comment out refreshTable(), CPU stays near 0%, which indicates that it is the problem.
Why is so much CPU being used? (I have 8 cores by the way).
Is there another way to redraw just a single row without redrawing the whole table?
I even tried a 'hacky' method -- changing all values of the Alerts and then resetting them back to cause JavaFx to detect the change and redraw, but CPU was again at the same levels.
Probably the most efficient way to change the color of a table row is to use a table row factory, have the table row it creates observe the appropriate property, and update one or more CSS PseudoClass states as appropriate. Then just define the colors in an external css file.
Here's a standalone version of the application you described. I just used a Timeline to perform the "flashing new alerts", which is less code; but use the executor as you have it if you prefer. The key idea here is the table row factory, and the pseudoclass state it manipulates by observing the property. On my system, if I fill the entire table with new (flashing) rows, the CPU doesn't exceed about 35% (percentage of one core), which seems perfectly acceptable.
Note that PseudoClass was introduced in Java 8. In earlier versions of JavaFX you can achieve the same by manipulating the style classes instead, though you have to be careful not to duplicate any style classes as they are stored as a List. Anecdotally, the pseudoclass approach is more efficient.
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener.Change;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
import javafx.util.Duration;
public class AlertTableDemo extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Alert> table = new TableView<>();
table.getColumns().add(createColumn("Name", Alert::nameProperty));
table.getColumns().add(createColumn("Value", Alert::valueProperty));
TableColumn<Alert, Alert> resolveCol =
createColumn("Resolve", ReadOnlyObjectWrapper<Alert>::new);
resolveCol.setCellFactory(this::createResolveCell);
table.getColumns().add(resolveCol);
// just need a wrapper really, don't need the atomicity...
AtomicInteger alertCount = new AtomicInteger();
Random rng = new Random();
Button newAlertButton = new Button("New Alert");
newAlertButton.setOnAction( event ->
table.getItems().add(new Alert("Alert "+alertCount.incrementAndGet(),
rng.nextInt(20)+1)));
// set psuedo-classes on table rows depending on alert state:
table.setRowFactory(tView -> {
TableRow<Alert> row = new TableRow<>();
ChangeListener<Alert.State> listener = (obs, oldState, newState) ->
updateTableRowPseudoClassState(row, row.getItem().getState());
row.itemProperty().addListener((obs, oldAlert, newAlert) -> {
if (oldAlert != null) {
oldAlert.stateProperty().removeListener(listener);
}
if (newAlert == null) {
clearTableRowPseudoClassState(row);
} else {
updateTableRowPseudoClassState(row, row.getItem().getState());
newAlert.stateProperty().addListener(listener);
}
});
return row ;
});
// flash new alerts:
table.getItems().addListener((Change<? extends Alert> change) -> {
while (change.next()) {
if (change.wasAdded()) {
List<? extends Alert> newAlerts =
new ArrayList<>(change.getAddedSubList());
flashAlerts(newAlerts);
}
}
});
HBox controls = new HBox(5, newAlertButton);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);
BorderPane root = new BorderPane(table, null, null, controls, null);
Scene scene = new Scene(root, 800, 600);
scene.getStylesheets().add(
getClass().getResource("alert-table.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
private void flashAlerts(List<? extends Alert> newAlerts) {
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.5),
event -> {
for (Alert newAlert : newAlerts) {
if (newAlert.getState()==Alert.State.UNRESOLVED_NEW_0) {
newAlert.setState(Alert.State.UNRESOLVED_NEW_1);
} else if (newAlert.getState() == Alert.State.UNRESOLVED_NEW_1){
newAlert.setState(Alert.State.UNRESOLVED_NEW_0);
}
}
}));
timeline.setOnFinished(event -> {
for (Alert newAlert : newAlerts) {
if (newAlert.getState() != Alert.State.RESOLVED) {
newAlert.setState(Alert.State.UNRESOLVED_OLD);
}
}
});
timeline.setCycleCount(20);
timeline.play();
}
private void clearTableRowPseudoClassState(Node node) {
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new"), false);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new-alt"), false);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-old"), false);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("resolved"), false);
}
private void updateTableRowPseudoClassState(Node node, Alert.State state) {
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new"),
state==Alert.State.UNRESOLVED_NEW_0);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-new-alt"),
state==Alert.State.UNRESOLVED_NEW_1);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("unresolved-old"),
state==Alert.State.UNRESOLVED_OLD);
node.pseudoClassStateChanged(PseudoClass.getPseudoClass("resolved"),
state==Alert.State.RESOLVED);
}
private TableCell<Alert, Alert> createResolveCell(TableColumn<Alert, Alert> col) {
TableCell<Alert, Alert> cell = new TableCell<>();
Button resolveButton = new Button("Resolve");
resolveButton.setOnAction(event ->
cell.getItem().setState(Alert.State.RESOLVED));
cell.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
cell.setAlignment(Pos.CENTER);
cell.graphicProperty().bind(
Bindings.when(cell.emptyProperty())
.then((Node)null)
.otherwise(resolveButton));
return cell ;
}
private <S, T> TableColumn<S, T> createColumn(String title,
Function<S, ObservableValue<T>> propertyMapper) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> propertyMapper.apply(cellData.getValue()));
col.setMinWidth(Region.USE_PREF_SIZE);
col.setPrefWidth(150);
return col ;
}
public static class Alert {
public enum State {
UNRESOLVED_NEW_0, UNRESOLVED_NEW_1, UNRESOLVED_OLD, RESOLVED
}
private final ObjectProperty<State> state = new SimpleObjectProperty<>();
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public final ObjectProperty<State> stateProperty() {
return this.state;
}
public final AlertTableDemo.Alert.State getState() {
return this.stateProperty().get();
}
public final void setState(final AlertTableDemo.Alert.State state) {
this.stateProperty().set(state);
}
public final StringProperty nameProperty() {
return this.name;
}
public final java.lang.String getName() {
return this.nameProperty().get();
}
public final void setName(final java.lang.String name) {
this.nameProperty().set(name);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
public Alert(String name, int value) {
setName(name);
setValue(value);
setState(State.UNRESOLVED_NEW_0);
}
}
public static void main(String[] args) {
launch(args);
}
}
alert-table.css:
.table-row-cell:resolved {
-fx-background: green ;
}
.table-row-cell:unresolved-old {
-fx-background: red ;
}
.table-row-cell:unresolved-new {
-fx-background: blue ;
}
.table-row-cell:unresolved-new-alt {
-fx-background: yellow ;
}

Preference activity seekbar

I have created a custom android seek bar from lukehorvat tutorial
and added to my preference xml file as below
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto" >
<PreferenceCategory
android:title="Color RGB channels"
android:order="100">
<com.heroku.android.SeekBarDialogPreference
android:defaultValue="20"
android:id="#+id/redchannel"
android:key="redchannel"
android:dialogMessage="Please select red channel:"
android:max="50"
android:title="Red channel"
custom:progressTextSuffix="%"
custom:min="1" />
<com.heroku.android.SeekBarDialogPreference
android:defaultValue="20"
android:dialogMessage="Please select green channel:"
android:max="50"
android:title="Select green channel"
custom:progressTextSuffix="%"
custom:min="1" />
<com.heroku.android.SeekBarDialogPreference
android:defaultValue="20"
android:dialogMessage="Please select blue channel:"
android:max="50"
android:title="Select blue channel"
custom:progressTextSuffix="%"
custom:min="1" />
</PreferenceCategory>
</PreferenceScreen>
And I have added to my preference activity these three seekbars as below
package com.heroku.android;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.view.View;
import android.widget.SeekBar;
public class Preferences extends PreferenceActivity
implements SharedPreferences.OnSharedPreferenceChangeListener {
#Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(com.yuldashev.android.R.xml.preferences);
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(
this);
}
#Override
protected void onResume() {
super.onResume();
}
#Override
protected void onDestroy() {
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
this);
super.onDestroy();
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
}
}
The problem is all of them refers to the same seekbardialog custom class and I cannot get the values for these three seekbars seperately. I have tried to seperate them by entitling #+id in xml file but it does not works for me by findviewbyID because the custom SeekBardialogPreference does not support such an option. For example if you adding and single SeekBar by id you do
SeekBar seek1=(SeekBar)findviewByID(resource)
and you get the progress value from seek1 object.
Is there any suggestion how to do the same with custom seekbardialog below
package com.heroku.android;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
/**
* A {#link DialogPreference} that provides a user with the means to select an integer from a {#link SeekBar}, and persist it.
*
* #author lukehorvat
*
*/
public class SeekBarDialogPreference extends DialogPreference
{
private static final int DEFAULT_MIN_PROGRESS = 0;
private static final int DEFAULT_MAX_PROGRESS = 100;
private static final int DEFAULT_PROGRESS = 0;
private int mMinProgress;
private int mMaxProgress;
private int mProgress;
private CharSequence mProgressTextSuffix;
private TextView mProgressText;
private SeekBar mSeekBar;
public SeekBarDialogPreference(Context context)
{
this(context, null);
}
public SeekBarDialogPreference(Context context, AttributeSet attrs)
{
super(context, attrs);
// get attributes specified in XML
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, com.yuldashev.android.R.styleable.SeekBarDialogPreference, 0, 0);
try
{
setMinProgress(a.getInteger(com.yuldashev.android.R.styleable.SeekBarDialogPreference_min, DEFAULT_MIN_PROGRESS));
setMaxProgress(a.getInteger(com.yuldashev.android.R.styleable.SeekBarDialogPreference_android_max, DEFAULT_MAX_PROGRESS));
setProgressTextSuffix(a.getString(com.yuldashev.android.R.styleable.SeekBarDialogPreference_progressTextSuffix));
}
finally
{
a.recycle();
}
// set layout
setDialogLayoutResource(com.yuldashev.android.R.layout.preference_seek_bar_dialog);
setPositiveButtonText(android.R.string.ok);
setNegativeButtonText(android.R.string.cancel);
setDialogIcon(null);
}
#Override
protected void onSetInitialValue(boolean restore, Object defaultValue)
{
setProgress(restore ? getPersistedInt(DEFAULT_PROGRESS) : (Integer) defaultValue);
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index)
{
return a.getInt(index, DEFAULT_PROGRESS);
}
#Override
protected void onBindDialogView(View view)
{
super.onBindDialogView(view);
TextView dialogMessageText = (TextView) view.findViewById(com.yuldashev.android.R.id.text_dialog_message);
dialogMessageText.setText(getDialogMessage());
mProgressText = (TextView) view.findViewById(com.yuldashev.android.R.id.text_progress);
mSeekBar = (SeekBar) view.findViewById(com.yuldashev.android.R.id.seek_bar);
mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener()
{
#Override
public void onStopTrackingTouch(SeekBar seekBar)
{
}
#Override
public void onStartTrackingTouch(SeekBar seekBar)
{
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
{
// update text that displays the current SeekBar progress value
// note: this does not persist the progress value. that is only ever done in setProgress()
String progressStr = String.valueOf(progress + mMinProgress);
mProgressText.setText(mProgressTextSuffix == null ? progressStr : progressStr.concat(mProgressTextSuffix.toString()));
}
});
mSeekBar.setMax(mMaxProgress - mMinProgress);
mSeekBar.setProgress(mProgress - mMinProgress);
}
public int getMinProgress()
{
return mMinProgress;
}
public void setMinProgress(int minProgress)
{
mMinProgress = minProgress;
setProgress(Math.max(mProgress, mMinProgress));
}
public int getMaxProgress()
{
return mMaxProgress;
}
public void setMaxProgress(int maxProgress)
{
mMaxProgress = maxProgress;
setProgress(Math.min(mProgress, mMaxProgress));
}
public int getProgress()
{
return mProgress;
}
public void setProgress(int progress)
{
progress = Math.max(Math.min(progress, mMaxProgress), mMinProgress);
if (progress != mProgress)
{
mProgress = progress;
persistInt(progress);
notifyChanged();
}
}
public CharSequence getProgressTextSuffix()
{
return mProgressTextSuffix;
}
public void setProgressTextSuffix(CharSequence progressTextSuffix)
{
mProgressTextSuffix = progressTextSuffix;
}
#Override
protected void onDialogClosed(boolean positiveResult)
{
super.onDialogClosed(positiveResult);
// when the user selects "OK", persist the new value
if (positiveResult)
{
int seekBarProgress = mSeekBar.getProgress() + mMinProgress;
if (callChangeListener(seekBarProgress))
{
setProgress(seekBarProgress);
}
}
}
#Override
protected Parcelable onSaveInstanceState()
{
// save the instance state so that it will survive screen orientation changes and other events that may temporarily destroy it
final Parcelable superState = super.onSaveInstanceState();
// set the state's value with the class member that holds current setting value
final SavedState myState = new SavedState(superState);
myState.minProgress = getMinProgress();
myState.maxProgress = getMaxProgress();
myState.progress = getProgress();
return myState;
}
#Override
protected void onRestoreInstanceState(Parcelable state)
{
// check whether we saved the state in onSaveInstanceState()
if (state == null || !state.getClass().equals(SavedState.class))
{
// didn't save the state, so call superclass
super.onRestoreInstanceState(state);
return;
}
// restore the state
SavedState myState = (SavedState) state;
setMinProgress(myState.minProgress);
setMaxProgress(myState.maxProgress);
setProgress(myState.progress);
super.onRestoreInstanceState(myState.getSuperState());
}
private static class SavedState extends BaseSavedState
{
int minProgress;
int maxProgress;
int progress;
public SavedState(Parcelable superState)
{
super(superState);
}
public SavedState(Parcel source)
{
super(source);
minProgress = source.readInt();
maxProgress = source.readInt();
progress = source.readInt();
}
#Override
public void writeToParcel(Parcel dest, int flags)
{
super.writeToParcel(dest, flags);
dest.writeInt(minProgress);
dest.writeInt(maxProgress);
dest.writeInt(progress);
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>()
{
#Override
public SavedState createFromParcel(Parcel in)
{
return new SavedState(in);
}
#Override
public SavedState[] newArray(int size)
{
return new SavedState[size];
}
};
}
}
Thank you all!

Dynamically adding data in Hashmap to TableView in JavaFX

I have a map mapping symbols to prices.I want to display a table with one column containing keys and the other column containing corresponding values in JavaFX
public class myMap {
Map<Symbol, Price> map;
}
I want to display a table like the following
Symbol | Price
I guess it can be done by using TableView with CallBack.
Since I've already done something similar to this I'll add my code.
import java.util.Map;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableMap;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.converter.NumberStringConverter;
public class MapTable extends Application {
#Override
public void start(Stage primaryStage) {
final ObservableMap<String, Number> obsMap = FXCollections.observableHashMap();
for (int i = 0; i < 3; i++) obsMap.put("key "+i, i*10d);
final TableView<ObservableMap.Entry<String, Number>> tv = new TableView(FXCollections.observableArrayList(obsMap.entrySet()));
tv.setEditable(true);
obsMap.addListener((MapChangeListener.Change<? extends String, ? extends Number> change) -> {
tv.setItems(FXCollections.observableArrayList(obsMap.entrySet()));
});
TableColumn<ObservableMap.Entry<String, Number>,String> keyCol = new TableColumn<>("key");
TableColumn<ObservableMap.Entry<String, Number>,Number> priceCol = new TableColumn<>("price");
tv.getColumns().addAll(keyCol,priceCol);
keyCol.setCellValueFactory((p) -> {
return new SimpleStringProperty(p.getValue().getKey());
});
keyCol.setCellFactory(TextFieldTableCell.forTableColumn());
keyCol.setOnEditCommit((TableColumn.CellEditEvent<Map.Entry<String,Number>, String> t) -> {
final String oldKey = t.getOldValue();
final Number oldPrice = obsMap.get(oldKey);
obsMap.remove(oldKey);
obsMap.put(t.getNewValue(),oldPrice);
});
priceCol.setCellValueFactory((p) -> {
return new ReadOnlyObjectWrapper<>(p.getValue().getValue());
});
priceCol.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));
priceCol.setOnEditCommit((TableColumn.CellEditEvent<Map.Entry<String,Number>, Number> t) -> {
obsMap.put(t.getTableView().getItems().get(t.getTablePosition().getRow()).getKey(),//key
t.getNewValue());//val);
});
Button btn1 = new Button();
btn1.setText("Add data");
btn1.setOnAction((ActionEvent event) -> {
obsMap.put("hi",100);
});
Button btn2 = new Button();
btn2.setText("verify data");
btn2.setOnAction((ActionEvent event) -> {
for (Map.Entry<String,Number> me : obsMap.entrySet())
System.out.println("key "+me.getKey()+" val "+me.getValue());
});
VBox root = new VBox(tv,btn1,btn2);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Map Table test");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Hope this can help.
public class Demo {
public static void main(String[] args) {
// Use Java Collections to create the List.
Map<String,String> map = new HashMap<String,String>();
// Now add observability by wrapping it with ObservableList.
ObservableMap<String,String> observableMap = FXCollections.observableMap(map);
observableMap.addListener(new MapChangeListener() {
#Override
public void onChanged(MapChangeListener.Change change) {
System.out.println("Detected a change! ");
}
});
// Changes to the observableMap WILL be reported.
observableMap.put("key 1","value 1");
System.out.println("Size: "+observableMap.size());
// Changes to the underlying map will NOT be reported.
map.put("key 2","value 2");
System.out.println("Size: "+observableMap.size());
}
}
http://docs.oracle.com/javafx/2/collections/jfxpub-collections.htm

Drag and Drop vbox element with show snapshot in javafx

I want drag an element in vbox as a parent and show node moving during drag and drop of element, how can do this with The slightest change.
Just register mouse listeners with the elements of the VBox. You want to call startFullDrag() on the node on a dragDetected event, and rotate the child nodes of the VBox on a dragReleased event. You can use the dragEntered and dragExited events if you want to give visual hints to the user about the drag.
See the API docs for more.
Simple example (code is way cleaner in JavaFX 8, btw):
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseDragEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
final VBox root = new VBox(5);
final ScrollPane scroller = new ScrollPane();
scroller.setContent(root);
final Scene scene = new Scene(scroller,400,200);
for (int i=1; i<=20; i++) {
final Label label = new Label("Item "+i);
addWithDragging(root, label);
}
// in case user drops node in blank space in root:
root.setOnMouseDragReleased(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
int indexOfDraggingNode = root.getChildren().indexOf(event.getGestureSource());
rotateNodes(root, indexOfDraggingNode, root.getChildren().size()-1);
}
});
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
private void addWithDragging(final VBox root, final Label label) {
label.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
label.startFullDrag();
}
});
// next two handlers just an idea how to show the drop target visually:
label.setOnMouseDragEntered(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
label.setStyle("-fx-background-color: #ffffa0;");
}
});
label.setOnMouseDragExited(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
label.setStyle("");
}
});
label.setOnMouseDragReleased(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
label.setStyle("");
int indexOfDraggingNode = root.getChildren().indexOf(event.getGestureSource());
int indexOfDropTarget = root.getChildren().indexOf(label);
rotateNodes(root, indexOfDraggingNode, indexOfDropTarget);
event.consume();
}
});
root.getChildren().add(label);
}
private void rotateNodes(final VBox root, final int indexOfDraggingNode,
final int indexOfDropTarget) {
if (indexOfDraggingNode >= 0 && indexOfDropTarget >= 0) {
final Node node = root.getChildren().remove(indexOfDraggingNode);
root.getChildren().add(indexOfDropTarget, node);
}
}
public static void main(String[] args) {
launch(args);
}
}
This is an addendum to #James_D's excellent answer
This shows how to add an image preview to the draggable node as #James_D suggests in his comment:
private void addPreview(final VBox root, final Label label) {
ImageView imageView = new ImageView(label.snapshot(null, null));
imageView.setManaged(false);
imageView.setMouseTransparent(true);
root.getChildren().add(imageView);
root.setUserData(imageView);
root.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
imageView.relocate(event.getX(), event.getY());
}
});
}
private void removePreview(final VBox root) {
root.setOnMouseDragged(null);
root.getChildren().remove(root.getUserData());
root.setUserData(null);
}
Call addPreview() in label.setOnDragDetected(). Call removePreview() in label.setOnMouseDragReleased() and root.setOnMouseDragReleased().
There is a much better solution that is far cleaner now.
// Root is the node you want to drag, not the scene root.
root.setOnDragDetected(mouseEvent -> {
final ImageView preview = new ImageView(root.snapshot(null, null));
final Dragboard db = root.startDragAndDrop(TransferMode.ANY);
db.setContent( // Set your content to something here.
);
db.setDragView(preview.getImage());
mouseEvent.consume();
});