So I currently have a 3x3 gridpane of which I add a pane with a white background to each space on the grid.
What I am trying to do is have it so that if a user clicks on one of these spaces, the white pane will change to another color. Then if the user were to click on the space again, the pane would change back to white. If clicked again, then it would change to that color again, and so on.
In short, a click would cause an action, and the next click, and those after it would reverse/undo the previous action.
However, I can only get the initial click to work with this. Everything else I've thought to add to this hasn't worked.
#Override
public void handle(MouseEvent me) {
if (me.getClickCount() == 1) {
pane.setBackground(new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY)));}
Any help would be really appreciated!
If it really has to be a Pane, you could try to write a custom Pane which will make it easier for you to control its behaviour:
class MyPane extends Pane{
private Background standard, other;
public MyPane(){
standard = new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY));
other = new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY));
this.setBackground(standard);
}
public void changeColor(){
if(this.getBackground().equals(standard){
this.setBackground(other);
else{
this.setBackground(standard);
}
}
public void setBackground(Background bckgrnd){
this.other = bckgrnd;
}
If you use this class instead of a standard Pane, you are able to control the color changing simply via
#Override
public void handle(MouseEvent me){
myPane.changeColor();
}
If you would use the Rectangleclass, you could use the following code:
#Override
public void handle(MouseEvent me){
if(rectangle.getFill() == standard){
rectangle.setFill(other);
}else{
rectangle.setFill(standard);
}
provided that you defined the 2 Paint variables standard and other, e.g. :
private final Paint standard = Color.WHITE;
private Paint other = Color.RED;
see Color
Related
I'm creating an image manipulation program in JavaFX using FXML to organize the UI. It currently supports things like loading images, drawing, and saving those changes.
I do not know how/cannot find a way to represent a Stack of Canvas' in the FXML document. My goal is to have a stack of Canvas' that will allow me to undo the changes the user has made by simply clearing the top layer in the stack of canvas'. The idea is that each edit would reside on it's own canvas.
Below is some code from the FXML document. Here is the center pane of my border pane. This is the center of the image manipulating program. Inside of it I have a stack pane so that I can overlay things. The two comments are what I would expect to be able to do but those attempts do not work.
<!-- center pane -->
<center>
<StackPane>
<Canvas fx:id="currCanvas" />
<!-- <Canvas fx:id="canvasStack" /> -->
<!-- <Stack fx:id="canvasStack" /> -->
<Canvas fx:id="previewCanvas" />
</StackPane>
</center>
If I was going to implement this without the FXML document it would be much simpler but it would be more difficult to organize the UI. My confusion is that I do not know how to accomplish this using FXML.
Thanks
All you really need is one Canvas but whenever an edit is made, you should make a snapshot of the canvas or make a copy of the pixels in the canvas then push that array of pixels or object containing the pixel to a Stack. That would be a bad idea because making a copy of the canvas will be very expensive both computationally and memory wise. While feasible, I would advise against it.
To me the best and easiest way to deal with "undo operations" is to draw first on a BufferedImage then draw that image on the Canvas as explained here and here and second to introduce the concept of Action in your application. For instance a "paint action" would look something like:
interface Action
{
//Used to re-apply action after it was undone
public void apply();
//Undo the action
public void undo();
}
class PaintAction() implements Action
{
static class Pixel
{
final int x;
final int y;
final int oldColor;
final int newColor;
PixelPos(int x, int y, int oldColor, int newColor)
{
this.x = x;
this.y = y;
this.oldColor = oldColor;
this.newColor = newColor;
}
}
List<Pixel> affectedPixels;
PaintAction()
{
affectedPixels = new ArrayList<>();
}
#Override
public void apply(Canvas canvas)
{
for (Pixel pixel : pixel)
{
//draw new pixel's color on the canvas
}
}
#Override
public void undo(Canvas canvas)
{
for (Pixel pixel : pixel)
{
//draw old pixel's color on the canvas
}
}
public void addPixel(Pixel pixel)
{
affectedPixels.add(Pixel);
}
}
So when the user presses the mouse button to start painting, you create a new PaintAction then whenever the user moves the mouse, you would create a new Pixel object and add it to the list of "affected pixels by the PaintAction" then proceed to change the color of the pixel in the BufferedImage.
Then all you would need will be to keep a stack of Action and apply, undo them as you see fit.
Hope that make sense, cheers.
Simply use a Pane. This way there are no issues with aligning the children. Panes don't provide a Stack of children, but a List can be used for stack operation too, although the removing the last item from the list is a bit more difficult than for a stack, but simple enough.
The following code simply adds circles and rectangles, but you could replace this with adding Canvases instead:
#Override
public void start(Stage primaryStage) {
Button doBtn = new Button("draw");
Button undo = new Button("undo");
undo.setDisable(true);
Pane stack = new Pane();
stack.setPrefSize(400, 400);
VBox root = new VBox(new HBox(doBtn, undo), stack);
doBtn.setOnAction(new EventHandler<ActionEvent>() {
private int count = 0;
#Override
public void handle(ActionEvent event) {
// create & add some node
Node addedNode;
switch (count % 2) {
case 0:
addedNode = new Circle(count * 5 + 5, count * 5 + 5, 5, Color.BLUE);
break;
case 1:
Rectangle rect = new Rectangle(count * 5, 390 - count * 5, 10, 10);
rect.setFill(Color.RED);
addedNode = rect;
break;
default:
return;
}
stack.getChildren().add(addedNode);
undo.setDisable(false);
count++;
}
});
undo.setOnAction(evt -> {
// remove last child
List<Node> children = stack.getChildren();
children.remove(children.size() - 1);
// check, if undo button needs to be disabled
undo.setDisable(children.isEmpty());
});
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
Note that I wouldn't recommend creating new Canvases for every operation though, since this could lead to memory issues pretty fast...
I have a scrollPane with pannable enabled. When panning with a large image the transformation "lags" behind the mouse making for a "blocky" transformation. Is there a way to fix this? There is not much code that is really relevant here to post if you need more specifics just ask.
I realize this is an old post, but others might need help in this area. What I did to solve this problem is to create a Transition object, and under the interpolate function, set the ScrollPane vValue or hValue equal to itself plus 0.001 depending on how fast you want to pan. vValue and hValue are the viewing locations on the ScrollPane, so basically you're just slowly incrementing what your viewing, so it looks like panning but its not. Here's an example from a project I'm working on where you can "pan" down or up with KeyEvents.
#FXML
ScrollPane scroll;
private Transition down;
private Transition up;
public void initialize(){
this.down = new Transition() {
{
setCycleDuration(Duration.INDEFINITE);
}
#Override
protected void interpolate(double v) {
scroll.setVvalue(scroll.getVvalue()+0.001);
}
};
this.up = new Transition() {
{
setCycleDuration(Duration.INDEFINITE);
}
#Override
protected void interpolate(double v) {
scroll.setVvalue(scroll.getVvalue()-0.001);
}
};
}
#FXML
private void handleKeyPress(KeyEvent event){
if(event.getCode() == KeyCode.S){
down.play();
}
if(event.getCode() == KeyCode.W){
up.play();
}
}
#FXML
private void handleKeyRelease(){
this.down.stop();
this.up.stop();
}
Been trying around and searching but couldn't find any solution, so I finally decided to give up and ask further...
Creating a javafx app, I load tiles in a TilePane.
This tiles are clickable and lead to a details page of their respective content.
On each tile, if they do belong to a certain pack, I do display the pack name, that is also clickable and lead to a page showing that specific pack content.
So that means the container, the tile, that is a Pane is clickable and on top of it I have a Label that is claickable also. What happens is when I do click the Label, it also triggers the Pane onMousePressed()... Here is a part of the tile creation code, the part focused on the onMousePressed(). I tried to make the Pane react by double click and the Label by single, it works, but I want to Pane to open with a single click.
I would be more than thankfull for any ideas how to solve that.
public DownloadTile (Downloadable upload, MainApp mainApp) {
_mainApp = mainApp;
_upload = upload;
_tile = new Pane();
_tile.setPrefHeight(100);
_tile.setPrefWidth(296);
_tile.setStyle("-fx-background-color: #ffffff;");
_tile.setCursor(Cursor.HAND);
}
public void refresh() {
_tile.getChildren().clear();
_tile.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.isPrimaryButtonDown() /*&& event.getClickCount() == 2*/) {
_mainApp.showDownloadDialog(dt, _upload);
}
}
});
if (_upload.getPack() != null) {
Label pack = new Label();
pack.setText(_upload.getPack());
pack.getStyleClass().add("pack-link");
pack.setCursor(Cursor.HAND);
pack.relocate(10, 48);
_tile.getChildren().add(pack);
pack.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.isPrimaryButtonDown()) {
_mainApp.showPackPage(_upload);
}
}
});
}
}
Your label will receive the mouseclick first (since it's on top), so after you have processed the click, you can stop it from being passed down the chain using 'consume':
pane.setOnMouseClicked(
(Event event) -> {
// process your click here
System.out.println("Panel clicked");
pane.requestFocus();
event.consume();
};
I have a legacy swing application that I need to add touch gestures to,specifically pinch to zoom and touch and drag.
I tried the SwingNode of JDK 8 and I can run the swing application there, but the display performance was cut by more than 50% which won't work. SwingTextureRenderer in MT4J has the same issue and that is without even trying to redispatch touch events as mouse events.
I thought about a glass pane approach using a JavaFX layer on top and capturing the touch events and attempting to dispatch them as mouse events to the Swing app underneath.
Does anyone have an alternative approach? The target platform is windows 8.
Bounty coming as soon as Stackoverflow opens it up. I need this one pretty rapidly.
EDIT:
Here is what I tried with SwingNode (the mouse redispatch didn't work). The SwingNode stuff might be a distraction from the best solution so ignore this if you have a better idea for getting touch into swing:
#Override
public void start(Stage stage) {
final SwingNode swingNode = new SwingNode();
createAndSetSwingContent(swingNode);
StackPane pane = new StackPane();
pane.getChildren().add(swingNode);
stage.setScene(new Scene(pane, 640, 480));
stage.show();
}
private void createAndSetSwingContent(final SwingNode swingNode) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
UILib.setPlatformLookAndFeel();
// create GraphViewTouch
String datafile = null;
String label = "label";
final JPanel frame = GraphView.demoFrameless(datafile, label);
swingNode.setContent(frame);
swingNode.setOnZoom(new EventHandler<ZoomEvent>() {
#Override public void handle(ZoomEvent event) {
MouseWheelEvent me = new MouseWheelEvent(frame, 1, System.currentTimeMillis(), 0, (int)Math.round(event.getSceneX()), (int)Math.round(event.getSceneY()), (int)Math.round(event.getScreenX()), (int)Math.round(event.getScreenY()), (int)Math.round(event.getZoomFactor()), false, MouseWheelEvent.WHEEL_UNIT_SCROLL, (int)Math.round(event.getZoomFactor()), (int)Math.round(event.getZoomFactor()), event.getZoomFactor());
frame.dispatchEvent(me);
System.out.println("GraphView: Zoom event" +
", inertia: " + event.isInertia() +
", direct: " + event.isDirect());
event.consume();
}
});
}
});
}
You can use JNA to parse the messages from Windows.
Some documentation on the multi touch events from Microsoft:
https://learn.microsoft.com/en-us/windows/desktop/wintouch/wm-touchdown
Some documentation on how to do it in Java and sample code:
https://github.com/fmsbeekmans/jest/wiki/Native-Multitouch-for-AWT-component-(Windows)
hmm... this is a tough one, but there is a chance.
What I would do is make multiple MouseListener classes (relative to the number of mouse events you want to pick up), and than create some sort of system to detect certain adjustments, eg. (zoom)
1st listener:
public void mousePressed(MouseEvent e){
//Set click to true
clk = true;
//set first mouse position
firstPos = window.getMousePosition();
}
public void mouseReleased(MouseEvent e){
//set second mouse position
secondPos = window.getMousePosition();
}
Second Listener
public void mousePressed(MouseEvent e){
//set clicked to true
clk = true;
//set first mouse position (listener 2)
firstPos = window.getMousePosition();
}
public void mouseReleased(MouseEvent e){
//set second mouse position
secondPos = window.getMousePosition();
}
Main handler
if(Listener1.get1stMousePos() < Listener1.get2ndMousePos() && Listener2.get1stMousePos() < Listener2.get2ndMousePos() && Listener1.clk && Listener2.clk){
zoomMethod((Listener1.get1stMousePos - Listener1.get2ndMousePos()) + (Listener1.get1stListener2.get2ndMousePos());
}
And than just do this to add it to the window:
window.addMouseListener(Listener1);
window.addMouseListener(Listener2);
Hope you find a way.
For the pinch-to-zoom:
Try the GestureMagnificationListener by guigarage.com. It provides a method:
public void magnify(GestureMagnificationEvent me){...}
I have a Swing app with a glass pane over a map.
It paints dots at certain positions. When I click somewhere on the map, and the glass pane receives the message CONTROLLER_NEW_POLYGON_MARK I
want do display an additional dot at the position specified in the event data (see MyGlassPane.propertyChange).
The glass pane class is called MyGlassPane. Using the debugger I validated that addPointToMark is actually called in propertyChange.
But no additional dots appear on the screen.
How can I change the code so that PointSetMarkingGlassPane.paintComponent is called whenever an event (IEventBus.CONTROLLER_NEW_POLYGON_MARK) is fired?
public class PointSetMarkingGlassPane extends JComponent implements IGlassPane {
private final ILatLongToScreenCoordinatesConverter latLongToScreenCoordinatesConverter;
private final List<Point.Double> pointsToMark = new LinkedList<Point.Double>();
public PointSetMarkingGlassPane(final ILatLongToScreenCoordinatesConverter aConverter) {
this.latLongToScreenCoordinatesConverter = aConverter;
}
protected void addPointToMark(final Point.Double aPoint)
{
if (aPoint != null)
{
pointsToMark.add(aPoint);
}
}
#Override
protected void paintComponent(final Graphics aGraphics) {
for (final Point.Double pointToMark : pointsToMark)
{
final Point positionInScreenCoords = latLongToScreenCoordinatesConverter.getScreenCoordinates(pointToMark);
drawCircle(aGraphics, positionInScreenCoords, Color.red);
}
}
private void drawCircle(Graphics g, Point point, Color color) {
g.setColor(color);
g.fillOval(point.x, point.y, 10, 10);
}
}
public class MyGlassPane extends PointSetMarkingGlassPane implements PropertyChangeListener {
public MyGlassPane(ILatLongToScreenCoordinatesConverter aConverter) {
super(aConverter);
addPointToMark(DemoGlassPane.ARTYOM);
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (IEventBus.CONTROLLER_NEW_POLYGON_MARK.equals(evt.getPropertyName()))
{
addPointToMark((Point.Double)evt.getNewValue());
invalidate();
}
}
}
As I think invalidate() only flags your component to check sizes and layout. You should call repaint() to repaint your pane.
Also I am wondering why you use propertyChangeListener for mouse clicks. I would prefer just simple mouse listener + MouseAdapter and MouseEvent x, y, buttons state.
invalidate() probably won't help you, as it flags a component for layout changes, not painting changes. Why not call repaint() instead?
For better performance, you could call the repaint method which takes a Rectangle (or four ints representing a rectangle), so that only the newly added point is repainted; I would suggest changing the return type of addPointToMark from void to java.awt.Point, and have it return the result of latLongToScreenCoordinatesConverter.getScreenCoordinates, so MyGlassPane can derive a rectangle from that Point which can then be passed to a repaint method.