I installed a Layer in the GlassPane like that:
MobileApplication.getInstance().getGlassPane().getLayers().add(myLayer);
Now I would expect the content of myLayer to be alligned in the center of the glassPane like it is when I add a layer to View:
view.getLayers().add(myLayer)
Although alignment is set to center I get the following result:
I noticed the layoutBounds of the layer added to glassPane being all "0", while the layoutBounds of the layer in view are identical to the view layoutBounds.
Furthermore I don't need to call layer.show() as stated in the gluon documentation ("Showing a Layer is then achieved either by calling show(), ..."), because the layer is immediately shown after it is added to a layer.
Am I missing something?
I suggest you have a look at the detailed documentation here, and not only to the JavaDoc.
There you will find a more detailed explanation on how layers can be added and created.
The preferred way is by using addLayerFactory(), like:
#Override
public void init() {
addViewFactory(BASIC_VIEW, () -> new BasicView(BASIC_VIEW));
addLayerFactory("My Layer", () -> new SidePopupView(new StackPane(new Button("Side"))));
}
The layer will be hidden unless you show it with MobileApplication.getInstance().showLayer("My Layer").
You can create your own Layer implementation, like:
private class MyLayer extends Layer {
private final Node root;
private final double size = 150;
public MyLayer() {
root = new StackPane(new Button("A custom layer"));
root.setStyle("-fx-background-color: white;");
getChildren().add(root);
getGlassPane().getLayers().add(this);
}
#Override
public void layoutChildren() {
root.setVisible(isShowing());
if (!isShowing()) {
return;
}
root.resize(size, size);
resizeRelocate((getGlassPane().getWidth() - size)/2, (getGlassPane().getHeight()- size)/2, size, size);
}
}
and add it as well to the factory:
addLayerFactory("My Layer", () -> new MyLayer());
But notice you will have to resize and relocate it, otherwise you will get 0,0 location as in your picture, and take care of its visibility.
Or you can use built-in layers, like SidePopupView, and you won't need to worry about these more low level details.
Related
This is the background for my question:
I have a GUI with an accordion with many TitledPanes, and each Titledpane contains a spreadsheetView from the controlsFX package.
There is a search-function in the code, where a Titledpane is opened and a specific cell in the spreadsheetView is opened for text input using the edit method of the spreadsheetcell type.
If the TitledPane is already open, this works fine, but if it must open first then the call of the edit-method fails. (The program is actually written in scalafx, but I don't think that matters here because scalafx is just a wrapper of javaFX and calls all the javaFX methods.)
Someone from the scalafx user group found out, that when I put in a wait time of 350ms (The animation time of the TitledPane is 300ms) then the call of 'edit' on the cell succeeds. He thought that the call fails, when the rendering of the content of the TitledPane is not complete.
This is also true when I turn the animation for the TitledPane off. In this case, it is sufficient to wait for 50ms, which does not work when animation is on.
Anyway - I am concerned about just waiting 350ms and hoping that this will always work. Which brings me back to the question: How can I tell that the rendering inside the TitledPane (or the spreadsheetView?) is complete so that I can safely call my edit method on the spreadsheetView?
Astonishingly, that doesn't seem to be supported.
The property that changes during the expand/collapse phase is the content's height: so a hack around might be to listen to it and start editing when fully expanded (which is a bit hacky in itself, could change due to layout constraints as well).
The example below simply initializes the fully expanded height after showing, listens to content's height property and starts editing when it reaches the fully expanded height.
The code:
public class TitledPaneEndOfExpansion extends Application {
private DoubleProperty expandedHeight = new SimpleDoubleProperty();
private TitledPane titled = new TitledPane();
private Parent createContent() {
titled.setText("Titled");
ListView<String> list = new ListView<>(FXCollections.observableArrayList("some", "content"));
list.setEditable(true);
list.setCellFactory(TextFieldListCell.forListView());
titled.setContent(list);
list.heightProperty().addListener((src, ov, nv) -> {
if (nv.doubleValue() == expandedHeight.get()) {
list.edit(0);
}
});
BorderPane content = new BorderPane(titled);
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
expandedHeight.set(((Region) titled.getContent()).getHeight());
}
public static void main(String[] args) {
launch(args);
}
}
Basically I like kleopatras idea, but unfortunately I can't figure out if this works for me or not.
At first I had some problems reading the code - only because my java knowledge is very limited. So I transferred it to scala. When I run it there, the call to edit works only sometimes (after startup it does not, when i clicked into a cell to edit it does). So I added a button that also calls edit - and it had the same behavior. So calling edit in general seems to have a problem in scalafx. But I learned something interesting here. I will now wait a few more days to see if anyone can think of anything else. If not then I will accept kleopatras solution.
For my own reference I add my not working scala-code here:
import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.beans.property.DoubleProperty
import scalafx.beans.value.ObservableValue
import scalafx.collections.ObservableBuffer
import scalafx.event.ActionEvent
import scalafx.scene.Scene
import scalafx.scene.control.cell.TextFieldListCell
import scalafx.scene.control.{Button, ListView, TitledPane}
import scalafx.scene.layout.BorderPane
object TitledPaneEndOfExpansion extends JFXApp {
val expandedHeight = new DoubleProperty()
val data: ObservableBuffer[String] = new ObservableBuffer[String]() ++= List("some", "content", "for", "testing")
stage = new JFXApp.PrimaryStage {
title = "JavaFX: edit after rendering test"
val list: ListView[String] = new ListView[String](data) {
editable = true
cellFactory = TextFieldListCell.forListView()
height.onChange { (source: ObservableValue[Double, Number], oldValue: Number, newValue: Number) =>
println("old height is: " + oldValue.doubleValue() + " new height is: " + newValue.doubleValue())
if (newValue.doubleValue() == expandedHeight.value) {
edit(1)
}
}
}
val titled: TitledPane = new TitledPane {
text = "titled"
content = list
}
scene = new Scene {
root = new BorderPane {
center = titled
bottom = new Button() {
text = "edit cell 1"
onAction = { _: ActionEvent => list.edit(1) }
}
}
}
expandedHeight.value = 400
list.edit(1)
}
}
Just as a preface, I am very new to programming in JavaFX. (We had an introduction to JavaFX in one of my classes last semester, and for the last month or so I've been working on making a simple game in JavaFX.)
An issue I've run into is trying to detect the collision of a Pane within one StackPane with the Pane inside another StackPane. Specifically, I have a "Player" node in the Game class ("Player" extends abstract "Sprite" which extends StackPane) along with some "Asset" nodes ("Asset" being an abstract parent class that like "Sprite" also extends StackPane). Both "Player" and every "Asset" node are comprised of an ImageView and Pane objects that are to be the "boundaries" of the node.
Here's how I'm attempting to track collisions in the Game class, but it's not working:
protected void update() {
// Call playerBoundsHandler in update cycle
for (Asset asset : this.gameArea.getAssets()) {
playerBoundsHandler(asset);
} // for
} // update
private void playerBoundsHandler(Asset asset) {
for (Pane boundary : asset.getAssetBoundaries()) {
if (player.getPlayerStandingAreaBox()
.getBoundsInParent()
.intersects(boundary.getBoundsInParent())) {
// do stuff here
} // if
} // for
} // playerBoundsHandler
I'm guessing there's something wrong with using getBoundsInParent() here since I'm trying to track the intersection of child nodes within two separate nodes, but I have no idea what the solution is. Is there something I need to do with getBoundsInLocal or some other method?
Here's the relevant part of the Player class for clarification's sake:
/**
* Player class constructor.
* Player class extends "Sprite" (abstract class)
* which extends StackPane.
*/
public Player(double xSpawn, double ySpawn) {
// Add Player Standing Box (a Pane situated at the feet of the Player sprite)
this.playerStandingAreaBox = new Pane();
// width, height, etc. set here
this.getChildren().add(playerStandingAreaBox);
this.setAlignment(playerStandingAreaBox, Pos.BOTTOM_CENTER);
} // Player constructor
public Pane getPlayerStandingAreaBox() {
return this.playerStandingAreaBox;
} // getPlayerStandingAreaBox
The Asset child classes follow a design almost identical to the Player class here. In case it's also needed for clarification, here's the "Highway" class:
public class Highway extends Asset {
public Highway(double translateX, double translateY) {
// call super here
setAssetBoundaries();
} // Highway constructor
#Override
setAssetBoundaries() {
Pane boundaryOne = new Pane();
// set boundaryOne settings
this.getChildren().add(boundaryOne);
this.assetBoundaries.add(boundaryOne);
Pane boundaryTwo = new Pane();
// set boundaryTwo settings
this.getChildren().add(boundaryTwo);
this.assetBoundaries.add(boundaryTwo);
} // setAssetBoundaries
/**
* assetBoundaries is an ArrayList<Asset> object also inherited.
* getAssetBoundaries() is inherited from the "Asset" class
* which returns assetBoundaries.
*/
The screenshot below shows my Player sprite (don't judge the awful pixel art! I already know the guy's right arm looks janky and the rifle looks ridiculous!) with his standing box highlighted in red, and the boundaries of a "Highway" Asset highlighted in yellow at both the very top and very bottom. I want to register when the Player's box intersects one of the boxes of the Highway.
Player and Highway
Thanks, James_D. The following change does exactly what I want it to do.
private void playerBoundsHandler(Asset asset) {
for (Pane boundary : asset.getAssetBoundaries()) {
Bounds boundaryBoundsInScene = boundary.localToScene(boundary.getBoundsInLocal());
Bounds playerStandingBoxBoundsInScene = player.getPlayerStandingBoxArea()
.localToScene(player.getPlayerStandingBoxArea().getBoundsInLocal());
if (boundaryBoundsInScene.intersects(playerStandingBoundsInScene)) {
// do stuff here
} // if
} // for
} // playerBoundsHandler
(This is my first post, sorry if I do something wrong...)
I am writing a program in Vala with which one can design a classroom.
I have decided to use GTK for the GUI (Vala integrates well with this),
and Cairo to draw the classroom diagram (GTK comes with this by default).
I have created a 'classroom' class (a subclass of Gtk.DrawingArea),
which currently should just display a square:
public class Classroom : DrawingArea
{
private delegate void DrawMethod();
public Classroom()
{
this.draw.connect((widget, context) => {
return draw_class(widget, context, context.stroke);
});
}
bool draw_class(Widget widget, Context context, DrawMethod draw_method)
{
context.set_source_rgb(0, 0, 0);
context.set_line_width(8);
context.set_line_join (LineJoin.ROUND);
context.save();
context.new_path();
context.move_to(10, 10);
context.line_to(30, 10);
context.line_to(30, 30);
context.line_to(10, 30);
context.line_to(10, 10);
context.close_path();
draw_method(); // Actually draw the lines in the buffer to the widget
context.restore();
return true;
}
}
I have also created a class for my application:
public class SeatingPlanApp : Gtk.Application
{
protected override void activate ()
{
var root = new Gtk.ApplicationWindow(this);
root.title = "Seating Plan";
root.set_border_width(12);
root.destroy.connect(Gtk.main_quit);
var grid = new Gtk.Grid();
root.add(grid);
/* Make our classroom area */
var classroom = new Classroom();
grid.attach(classroom, 0, 0, 1, 1);
//root.add(classroom);
root.show_all();
}
public SeatingPlanApp()
{
Object(application_id : "com.github.albert-tomanek.SeatingPlan");
}
}
And this is my main function:
int main (string[] args)
{
return new SeatingPlanApp().run(args);
}
I put my classroom widget into a Gtk.Grid, my layout widget of choice.
When I compile my code and run it, I get a blank window:
However, if I do not use Gtk.Grid, and just add my classroom using root.add() (which I have commented out), the classroom widget is displayed correctly:
Why does my widget not show up when adding it using Gtk.Grid?
What can I do to fix this?
The problem is that the cell is sized 0x0 pixels, because the grid doesn't know how much space your drawing area actually needs.
A simple solution is to just request some fixed size, try this:
var classroom = new Classroom();
classroom.set_size_request (40, 40);
PS: I got this idea by looking at other similar questions on SO, particularly this one.
I'm trying to use a TranslateTransition object in JavaFX to move an onscreen object in a LOGO program I am building. I have an onscreen TurtleDisplay object, which extends ImageView, and this is what I'm trying to move. The code to move it is here:
public void drawTurtle(TurtleData currentData) {
TurtleImage inList = getTurtleImage(currentData);
if (inList==null) {
TurtleImage temp = new TurtleImage(currentData.getX(),
currentData.getY(), currentData.getHeading(), turtleImage);
myTurtlesGroup.getChildren().add(temp);
myTurtlesList.add(temp);
}
else {
TranslateTransition tt = new TranslateTransition(Duration.seconds(3),inList);
tt.setFromX(inList.getX());
tt.setFromY(inList.getY());
tt.setToX(inList.getX()+currentData.getX());
tt.setToY(inList.getY()+currentData.getY());
tt.setCycleCount(Timeline.INDEFINITE);
tt.play();
}
}
This code, which is part of the front end, is called from the back end via a Listener on an ObservableList. The backend contains this ObservableList of TurtleData objects that contain the information necessary to move a turtle on screen -- which turtle to move, the coordinate to move to, and the rotation of the turtle. The code to call this is here:
ObservableList<TurtleData> myTurtles = FXCollections
.observableArrayList();
myTurtles.addListener(new ListChangeListener<TurtleData>() {
#Override
public void onChanged(Change<? extends TurtleData> c) {
myDisplay.getSelectedWorkspace().getTV().clearTurtles();
while (c.next()) {
for (TurtleData addItem : c.getAddedSubList()) {
myDisplay.getSelectedWorkspace().getTV().drawTurtle(addItem);
}
}
}
});
I have stepped through with a debugger and ensured that this code is called -- specifically, the tt.play() line is run. Nothing moves on screen. Does anyone have any idea what is wrong? Do I need to setup an Animation Timeline? Thank you for any help!
How can I get my actual object out of the Dragboard with getContent()? Is there any possibility to specify the type? Todo and Doing are FlowPanes - MyRectangle is an exammple of a custom component.
What I want to have:
Put an Object eg. an Rectangle with height, filled color etc. on the clipboard and get that object back from the board with height, color etc....
private static final DataFormat itemFormat = new DataFormat("custom.item");
MyRectangle myRectangle = generateRectangle();
myRectangle.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
Dragboard db = myRectangle
.startDragAndDrop(TransferMode.MOVE);
ClipboardContent content = new ClipboardContent();
content.put(taskFormat, myRectangle);
// Rectangle has height
System.out.println(myRectangle.getHeight());
TaskItem task = new TaskItem();
task.setTime(6);
content.put(itemFormat, task);
db.setContent(content);
event.consume();
}
});
myRectangle.setOnDragDone(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
event.consume();
}
});
doing.setOnDragOver(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
if (event.getGestureSource() != doing) {
event.acceptTransferModes(TransferMode.MOVE);
}
event.consume();
}
});
doing.setOnDragEntered(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
event.consume();
}
});
doing.setOnDragExited(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
event.consume();
}
});
doing.setOnDragDropped(new EventHandler<DragEvent>() {
public void handle(DragEvent event) {
final Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasContent(taskFormat)) {
MyRectangle rect2 = (MyRectangle) db.getContent(taskFormat);
System.out.println(rect2.getHeight());
todo.getChildren().remove(rect2);
doing.getChildren().add(rect2);
success = true;
// doing.getChildren().add(rectangle);
}
event.setDropCompleted(success);
event.consume();
}
});
private MyRectangle generateRectangle() {
final MyRectangle rect2 = new MyRectangle(0, 0, 10, 10);
rect2.setId("app");
rect2.setArcHeight(8);
rect2.setWidth(80);
rect2.setArcWidth(8);
rect2.setStrokeWidth(1);
rect2.setStroke(Color.WHITE);
rect2.setHeight(60);
return rect2;
}
Well, you really should be thinking about having a representation of the data (not the view of the data) which is the object that is dragged and dropped. You register a handler to detect the drag with a node (view of the data), set the data into the dragboard, and then on a successful drop create another view of the same data where you dropped it. Remove the previous Node for a move, don't remove it for a copy.
Unfortunately, it doesn't work.
See this
and vote for this bug/feature, which would allow you to do this directly.
Currently you can only place Java objects on the drag board if they implement Serializable. Since the JavaFX Properties do not implement Serializable, any class that uses those for data representation (which, imho, is any class you'd want to use to represent data you want to drag and drop around an application). And even if your class is Serializable, as I understand it, the object is serialized on placing it in the dragboard and deserialized when you remove it, which means you'd get a new object when you dragged and dropped, not a reference to the same object: that's probably not what you want. (If you copy something by drag and drop, then edit it, you probably want both copies to respect the edit.)
So, for now, I think the solution is to create a local repository of some kind and store the dragged object in it. This might be as simple as just an ObjectProperty<?> currentlyDraggedObject, or something more complex like the LocalDragboard I implemented at the bottom of the discussion referenced earlier. (This is nothing more than copying the code you'll find if you google "standard example of a typesafe heterogeneous container".)
I have to say, I find the way drag and drop is done a bit weird. Almost everything in JavaFX 2 and later was written in a very modern style of Java, with (almost) everything using generics very comfortably, some very nice concurrency APIs that were designed for the newer high-level concurrency APIs, all the event handling designed with an eye to more recent language developments such as lambda expressions and streams. The Bindings API even seems to tip its hat slightly towards the whole reactive programming movement. But drag and drop seems to have been designed as though the only data we would ever want to transfer by drag gestures were Strings, Images, and Files. It's as though the designers of the DnD API hadn't really got their heads around the idea that programmers would want to, you know, develop their own data representation classes.
So in the midst of this very modern-looking GUI framework, you have a DnD API that looks like it was designed in the late 90s (if that). Very strange.