JavaFX-8 Canvas within BorderPane - javafx-8

I'm pretty new to this whole JavaFX thing, as you can probably tell. This is an issue I ran into recently while playing around with the Canvas node. I've got a BorderPane as my root node, and I'd like to have a Canvas occupy all available space within the center of it. However, I'm having some trouble implementing that exact behavior. Here's the just of what I've been trying:
public void start(Stage stage){
BorderPane root=new BorderPane();
Canvas canvas=new Canvas(250,250);
//canvas.widthProperty().bind(root.widthProperty());
//canvas.heightProperty().bind(root.heightProperty());
GraphicsContext gc=canvas.getGraphicsContext2D();
new AnimationTimer(){
#Override
public void handle(long l){
double width=canvas.getWidth(),height=canvas.getHeight();
gc.clearRect(0,0,width,height);
gc.strokeRect(0,0,width,height);
gc.strokeLine(0,0,width,height);
gc.strokeLine(0,height,width,0);
}
}.start();
root.setCenter(canvas);
root.setBottom(new Button("Placeholder"));
root.setTop(new Button("Placeholder"));
stage.setScene(new Scene(root));
stage.show();
}
Instead of expanding as the Pane does, my Canvas just stays centered within it, retaining its original size. If the two commented lines near the top are re-added, the Canvas grows and shrinks as the Pane does, but without regarding the dimensions of its center (as expected). Is there a way to apply this sort of binding behavior to just the center of the BorderPane, or perhaps another way to do this entirely that I'm unaware of?

EDIT: Just found a much nicer solution today (19.05.2014):
http://fxexperience.com/2014/05/resizable-grid-using-canvas/
So much easier and shorter than mine -.-
Now my original approach:
I had the same problem as you do.
I found a really ugly workaround you can use, but maybe there is another method doing this...
My Workaround:
class MyCanvas {
private Canvas cv;
private StackPane box;
public MyCanvas(Stage stg) {
cv = new Canvas(500, 500);
box = new StackPane();
box.getChildren().add(cv);
//When the Stage size changes, the canvas size becomes resetted
//but the Hbox expandes automatically to fit the with again
//and so the canvas become resized to fit the HBox
stg.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) {
resize();
}
});
stg.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) {
resize();
}
});
box.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) {
adapt();
}
});
box.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) {
adapt();
}
});
paint();
}
private void paint(){
//Paint something ....
GraphicsContext ctx = cv.getGraphicsContext2D();
double w = cv.getWidth();
double h = cv.getHeight();
ctx.clearRect(0, 0, w, h);
ctx.setFill(Color.BLUE);
ctx.fillRect(0, 0, w, h);
}
//Add this HBox to your content pane
public StackPane getBox() {
return box;
}
public void resize(){
cv.setWidth(50);
cv.setHeight(50);
}
private void adapt(){
cv.setHeight(box.getHeight());
cv.setWidth(box.getWidth());
paint();
}
}
And in your Main:
public class Root extends Application{
public static void main(String[] args){ launch(args); }
public void start(Stage stg){
MyCanvas cv = new MyCanvas(stg);
BorderPane pane = new BorderPane();
pane.setCenter(pane.getBox());
stg.setScene(new Scene(pane));
stg.show();
}
}
Hope this helps you solving your problem.
nZeloT

Related

How to change or override the tooltip in JavaFX ColorPicker

I am using JavaFX ColorPicker in my application. As per my requirements, I have mapped the default colors on the color picker to a number. I want this number to be displayed as tooltip on hover over the color instead of hex value of the color. How can I achieve this?
//Part of Code
public void handleNodes(Circle circularNode) {
final Delta offset = new Delta();
circularNode.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
((Circle)(event.getSource())).setCursor(Cursor.HAND);
}
});
circularNode.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if(event.getButton().equals(MouseButton.SECONDARY)) {
System.out.println("Right click");
Circle parent = ((Circle)(event.getSource()));
final ContextMenu contextMenu = new ContextMenu();
MenuItem editLabel = new MenuItem("Edit Label");
editLabel.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Edit Label");
final ColorPicker colorPicker = new ColorPicker();
colorPicker.setStyle("-fx-border-radius: 10 10 10 10;"
+ "-fx-background-radius: 10 10 10 10;");
colorPicker.setValue((Color) parent.getFill());
colorPicker.showingProperty().addListener((obs,b,b1)->{
if(b1){
PopupWindow popupWindow = getPopupWindow();
javafx.scene.Node popup = popupWindow.getScene().getRoot().getChildrenUnmodifiable().get(0);
popup.lookupAll(".color-rect").stream()
.forEach(rect->{
Color c = (Color)((Rectangle)rect).getFill();
Tooltip.install(rect.getParent(), new Tooltip("Custom tip for "+c.toString()));
});
}
});
panelMain.getChildren().add(colorPicker);
}
});
This is really a hacky answer.
The first problem: you have to find the popup node on the scene once it shows up. But you won't... since its not in the same window!
Having a deep look at how ScenicView does it, the trick is getting the list of windows at that moment, but using a deprectated method:
private PopupWindow getPopupWindow() {
#SuppressWarnings("deprecation") final Iterator<Window> windows = Window.impl_getWindows();
while (windows.hasNext()) {
final Window window = windows.next();
if (window instanceof PopupWindow) {
return (PopupWindow)window;
}
}
return null;
}
Once you have the popup window, we can now check for all the Rectangle nodes using lookupAll and the CSS selector color-rect, to get their color, and install the tooltip over its parent container:
#Override
public void start(Stage primaryStage) {
ColorPicker picker = new ColorPicker();
StackPane root = new StackPane(picker);
Scene scene = new Scene(root, 500, 400);
primaryStage.setScene(scene);
primaryStage.show();
picker.showingProperty().addListener((obs,b,b1)->{
if(b1){
PopupWindow popupWindow = getPopupWindow();
Node popup = popupWindow.getScene().getRoot().getChildrenUnmodifiable().get(0);
popup.lookupAll(".color-rect").stream()
.forEach(rect->{
Color c = (Color)((Rectangle)rect).getFill();
Tooltip.install(rect.getParent(), new Tooltip("Custom tip for "+c.toString()));
});
}
});
}
This is what it looks like:
Based on the code posted by the OP after my first answer, and due to the substancial changes in the problem addressed, I'm adding a new answer that covers both situations:
The ColorPicker is embedded in the main scene, as a regular node
The ColorPicker is embedded in a ContextMenu
In the second situation, the proposed solution for the first one is no longer valid, since the window found will be the one with the context menu.
A task is required to keep looking for windows until the one with the ComboBoxPopupControl is found.
This is a full runnable example:
public class ColorPickerFinder extends Application {
ExecutorService findWindowExecutor = createExecutor("FindWindow");
#Override
public void start(Stage primaryStage) {
AnchorPane panCircles = new AnchorPane();
Scene scene = new Scene(panCircles, 400, 400);
final Random random = new Random();
IntStream.range(0,5).boxed().forEach(i->{
final Circle circle= new Circle(20+random.nextInt(80),
Color.rgb(random.nextInt(255),random.nextInt(255),random.nextInt(255)));
circle.setTranslateX(100+random.nextInt(200));
circle.setTranslateY(100+random.nextInt(200));
panCircles.getChildren().add(circle);
});
panCircles.setPrefSize(400, 400);
ColorPicker colorPicker = new ColorPicker();
panCircles.getChildren().add(colorPicker);
primaryStage.setScene(scene);
primaryStage.show();
// We add listeners AFTER showing the stage, as we are looking for nodes
// by css selectors, these will be available only after the stage is shown
colorPicker.showingProperty().addListener((obs,b,b1)->{
if(b1){
// No need for task in this case
getPopupWindow();
}
});
panCircles.getChildren().stream()
.filter(c->c instanceof Circle)
.map(c->(Circle)c)
.forEach(circle->{
circle.setOnMouseClicked(e->{
if(e.getButton().equals(MouseButton.SECONDARY)){
// We need a task, since the first window found is the ContextMenu one
findWindowExecutor.execute(new WindowTask());
final ColorPicker picker = new ColorPicker();
picker.setStyle("-fx-border-radius: 10 10 10 10;"
+ "-fx-background-radius: 10 10 10 10;");
picker.setValue((Color)(circle.getFill()));
picker.valueProperty().addListener((obs,c0,c1)->circle.setFill(c1));
final ContextMenu contextMenu = new ContextMenu();
MenuItem editLabel = new MenuItem();
contextMenu.getItems().add(editLabel);
editLabel.setGraphic(picker);
contextMenu.show(panCircles,e.getScreenX(),e.getScreenY());
}
});
});
}
private PopupWindow getPopupWindow() {
#SuppressWarnings("deprecation")
final Iterator<Window> windows = Window.impl_getWindows();
while (windows.hasNext()) {
final Window window = windows.next();
if (window instanceof PopupWindow) {
if(window.getScene()!=null && window.getScene().getRoot()!=null){
Parent root = window.getScene().getRoot();
if(root.getChildrenUnmodifiable().size()>0){
Node popup = root.getChildrenUnmodifiable().get(0);
if(popup.lookup(".combo-box-popup")!=null){
// only process ComboBoxPopupControl
Platform.runLater(()->{
popup.lookupAll(".color-rect").stream()
.forEach(rect->{
Color c = (Color)((Rectangle)rect).getFill();
Tooltip.install(rect.getParent(),
new Tooltip("Custom tip for "+c.toString()));
});
});
return (PopupWindow)window;
}
}
}
return null;
}
}
return null;
}
private class WindowTask extends Task<Void> {
#Override
protected Void call() throws Exception {
boolean found=false;
while(!found){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
found=(getPopupWindow()!=null);
}
return null;
}
}
private ExecutorService createExecutor(final String name) {
ThreadFactory factory = r -> {
Thread t = new Thread(r);
t.setName(name);
t.setDaemon(true);
return t;
};
return Executors.newSingleThreadExecutor(factory);
}
public static void main(String[] args) {
launch(args);
}
}
This will be the result after right clicking on a circle, and clicking on the color picker:

Why isn't my JFrame displaying anything from my Jpanel?

I'm working on creating a 3 man's morris board, but nothing is being displayed on the frame. It's empty despite having added my JPanel. Everything is fine if I used board = new JPanel(new GridLayout()); and do the following, but I wouldn't be able to draw the lines that would draw the board. I've looked over it a few times but can't seem to find a problem.
public class Project5 extends JFrame {
public final static int FRAME_WIDTH = 600;
public final static int FRAME_HEIGHT = 600;
private JButton jb[] = new JButton[9];
private Board board = new Board();
Project5(){
for(int i = 0; i<9; i++){
jb[i] = new JButton();
board.add(jb[i]);
}
add(board);
}
public static void main(String[] args) {
JFrame frame = new Project5();
frame.setTitle("Three Man's Morris");
frame.setSize(Project5.FRAME_WIDTH,Project5.FRAME_HEIGHT);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
class Board extends JPanel{
public Board(){
super();
setLayout(new GridLayout(3,0,Project5.FRAME_WIDTH,Project5.FRAME_HEIGHT));
}
#Override
public void paintComponents(Graphics g){
super.paintComponents(g);
g.drawLine(0, Project5.FRAME_WIDTH, 0, Project5.FRAME_HEIGHT);
g.drawLine(0, 0, 0, Project5.FRAME_HEIGHT);
g.drawLine(0,Project5.FRAME_WIDTH,0,0);
g.drawLine(0, Project5.FRAME_HEIGHT, Project5.FRAME_WIDTH, Project5.FRAME_HEIGHT);
g.drawLine(Project5.FRAME_WIDTH, 0, 0, Project5.FRAME_HEIGHT);
g.drawLine(Project5.FRAME_WIDTH,0,Project5.FRAME_WIDTH,Project5.FRAME_HEIGHT);
}
}
The problem is in your GridLayout() parameters :
GridLayout(rows,cols,horizontal_gap,vertical_gap)
in your case, both gaps are 600 (FRAME_WIDTH, FRAME_HEIGHT) !
The buttons are displayed, but they are outside the panel, try to lower the gap,
i.e. : setLayout(new GridLayout(3,0,0,0));
You should see the buttons.

Height of the middle element in a GWT HeaderPanel

I am using a GWT HeaderPanel. As a middle element, I am using a DockLayoutPanel as follows:
<g:DockLayoutPanel width="1200px" height="100%">
<g:west size="220">
<g:HTMLPanel styleName="{style.debug}">
something <br />
something <br />
</g:HTMLPanel>
</g:west>
</g:DockLayoutPanel>
The page renders fine but, if the browser window is shrunk vertically, the middle panel goes on top of the footer, which is of course not what you would want with a header panel.
I rather like to have the fixed footer, which is why I am not doing the whole thing using DockLayoutPanel. What am I doing wrong? Thanks!
EDIT: ok, actually, if the window is grown vertically, the middle panel still does not resize
EDIT2: The HeaderPanel is directly in the root panel and looks like this:
<g:HeaderPanel>
<my.shared:Header></my.shared:Header>
<my.shared:Middle></my.shared:Middle>
<my.shared:Footer></my.shared:Footer>
</g:HeaderPanel>
Layout panels 101: HeaderPanel is a RequiresResize panel, so it must either be put into a ProvidesResize panel, such as RootLayoutPanel, (or as the middle panel of a HeaderPanel [1]) or be given an explicit fixed size.
[1] HeaderPanel does not implement ProvidesResize because it only fulfills the contract for its middle panel.
The following approach worked for me. It's based on Zack Linder's advice
Google Web Toolkit ›layout panel problem
(1) Attach a HeaderLayoutPanel to your RootLayoutPanel. The headerLayoutPanel is a class you create that extends HeaderPanel and implements ProvidesResize().
import com.google.gwt.user.client.ui.HeaderPanel;
import com.google.gwt.user.client.ui.ProvidesResize;
import com.google.gwt.user.client.ui.SimpleLayoutPanel;
import com.google.gwt.user.client.ui.Widget;
public class HeaderLayoutPanel extends HeaderPanel implements ProvidesResize {
SimpleLayoutPanel contentPanel;
public HeaderLayoutPanel() {
super();
contentPanel=new SimpleLayoutPanel();
}
#Override
public void setContentWidget(Widget w) {
contentPanel.setSize("100%","100%");
contentPanel.setWidget(w);
super.setContentWidget(contentPanel);
}
public void onResize() {
int w=Window.getClientWidth();
int h=Window.getClientHeight();
super.setPixelSize(w, h);
}
}
(2) Next, instantiate a HeaderLayoutPanel. The header and footer widgets are assigned
a fixed height (e.g. menu bar height), and their width adjusts automatically to the
width of the panel. The center widget should be a ProvidesSize. I used a LayoutPanel.
For example,
public class AppViewer extends Composite implements MyApp.AppDisplay {
private HeaderLayoutPanel allContentsPanel;
MenuBar menuBarTop;
MenuBar menuBarBottom;
LayoutPanel dataPanel;
public AppViewer() {
allContentsPanel = new HeaderLayoutPanel();
menuBarTop = new MenuBar(false);
menuBarBottom = new MenuBar(false);
dataPanel = new LayoutPanel();
menuBarTop.setHeight("30px");
menuBarBottom.setHeight("20px");
allContentsPanel.setHeaderWidget(menuBarTop);
allContentsPanel.setFooterWidget(menuBarBottom);
allContentsPanel.setContentWidget(dataPanel);
initWidget(allContentsPanel);
}
#Override
public void doOnResize() {
allContentsPanel.onResize();
}
}
(3) The center widget (LayoutPanel) will hold the DeckLayoutPanel, which I defines in a
separate Composite (but you can do whatever you want). For example,
public class MyDataView extends Composite implements MyDataPresenter.DataDisplay {
private DockLayoutPanel pnlAllContents;
private HorizontalPanel hpnlButtons;
private HorizontalPanel hpnlToolbar;
private VerticalPanel pnlContent;
public MyView() {
pnlAllContents=new DockLayoutPanel(Unit.PX);
pnlAllContents.setSize("100%", "100%");
initWidget(pnlAllContents);
hpnlToolbar = new HorizontalPanel();
hpnlToolbar.setWidth("100%");
pnlAllContents.addNorth(hpnlToolbar, 30);
hpnlButtons = new HorizontalPanel();
hpnlButtons.setWidth("100%");
pnlAllContents.addSouth(hpnlButtons,20);
pnlContent=new VerticalPanel();
//center widget - takes up the remainder of the space
pnlAllContents.add(pnlContent);
...
}
}
(4) Finally everything gets tied together in the onModuleLoad() class: AppViewer generates
the display which is added to the RootLayoutPanel, and MyDataView generates a display.asWidget()
that is added to the container. For example,
public class MyApp implements EntryPoint,Presenter{
private HasWidgets container=RootLayoutPanel.get();
private static AppDisplay display;
private DataPresenter dataPresenter;
public interface AppDisplay extends Display{
#Override
Widget asWidget();
HasWidgets getContentContainer();
void doOnResize();
}
#Override
public void onModuleLoad() {
display=new AppViewer();
dataPresenter = new DataPresenter();
display.getContentContainer().add(dataPresenter.getDisplay().asWidget());
container.add(display.asWidget());
bind();
}
#Override
public void bind() {
Window.addResizeHandler(new ResizeHandler() {
#Override
public void onResize(ResizeEvent event) {
display.doOnResize();
}
});
}
#Override
public com.midasmed.client.presenter.Display getDisplay() {
return display;
}
}
Hope this helps!
Well, I haven't been able to solve this but, for future visitors: the same can be achieved (minus the problem I couldn't fix) using a DockLayourPanel with a north, center and south components.

drop widget beyond the AbsolutePanel gwt dnd

I'm trying to drop my draggable widgets out of the boundary panel (AbsolutePanel). In my case draggable widgets is an image. And I want to drop it, so that there will be visible only a part of the image, but when I drop it, and some parts of image beyond absolute panel, it drop automatically within absolute panel.
I tried :
dragController.setBehaviorConstrainedToBoundaryPanel(false);
and thought it means that I can drop it where ever I want, but it doesn't work.
And the working solution :)
Here is my code:
public class myEntripointClass implement EntryPOint{
AbsolutePanel droper;
public void onModuleLoad() {
Panel main = new AbsolutePanel();
droper = new AbsolutePanel();
droper.setHeight("300px");
droper.setWidth("500px");
main.add(droper);
content=new AbsolutePanel();
bt = new Button("Drag and drop it");
content.add(bt);
lb = new Label("Label drag and drop");
content.add(lb);
main.add(content);
manageDnD();
RootPanel.get().add(main);
}
private void manageDnD() {
PickupDragController dragController = new PickupDragController(
(AbsolutePanel) content, true);
dragController.makeDraggable(bt);
dragController.makeDraggable(lb);
dragController.addDragHandler(new DragHandler() {
#Override
public void onPreviewDragStart(DragStartEvent event)
throws VetoDragException {}
#Override
public void onPreviewDragEnd(DragEndEvent event) throws VetoDragException {}
#Override
public void onDragStart(DragStartEvent event) {
}
#Override
public void onDragEnd(DragEndEvent event) {
// TODO Auto-generated method stub
DragContext context = event.getContext();
int x=context.mouseX;
int y= context.mouseY;
droper.add(context.selectedWidgets.get(0),x,y);
}
});
NameDropController dropController = new NameDropController(droper);
dragController.registerDropController(dropController);
dragController.setBehaviorDragProxy(true);
}
and my DropController class is:
public class NameDropController extends AbstractDropController{
public NameDropController(Widget dropTarget) {
super(dropTarget);
}
#Override
public void onDrop(DragContext context) {
int x=getDiff(getDropTarget().getAbsoluteLeft(), context.mouseX);
int y=getDiff(getDropTarget().getAbsoluteTop(), context.mouseY);
((AbsolutePanel)getDropTarget()).add(context.selectedWidgets.get(0),x,y);
System.out.print("("+context.mouseX+"::,::"+context.mouseY+")");
}
#Override
public void onMove(DragContext context){
}
private int getDiff(int val1,int val2){
return Math.abs(val1-val2);
}
}

Drag and Drop in GWT 2.4

I have a custom widget that is actually an image, and i would like to be able to drag it inside an AbsolutePanel and get its coordinates every time. I would like to use the new DND API from GWT 2.4, but i'm having a hard time to implement it. Can someone propose the basic steps i must take?
The new DnD API introduced with GWT 2.4 doesn't currently support the AbsolutePanel (see the implementations of the HasAllDragAndDropHandlers interface). Looking at the implementation of FocusPanel it shouldn't be too hard to enable it for other panels.
Here's a quick proof of concept on how to solve your problem:
public void onModuleLoad() {
DragImage image = new DragImage();
image.setUrl(Resources.INSTANCE.someImage().getSafeUri());
final DropAbsolutePanel target = new DropAbsolutePanel();
target.getElement().getStyle().setBorderWidth(1.0, Unit.PX);
target.getElement().getStyle().setBorderStyle(BorderStyle.SOLID);
target.getElement().getStyle().setBorderColor("black");
target.setSize("200px", "200px");
// show drag over effect
target.addDragOverHandler(new DragOverHandler() {
#Override
public void onDragOver(DragOverEvent event) {
target.getElement().getStyle().setBackgroundColor("#ffa");
}
});
// clear drag over effect
target.addDragLeaveHandler(new DragLeaveHandler() {
#Override
public void onDragLeave(DragLeaveEvent event) {
target.getElement().getStyle().clearBackgroundColor();
}
});
// enable as drop target
target.addDropHandler(new DropHandler() {
#Override
public void onDrop(DropEvent event) {
event.preventDefault();
// not sure if the calculation is right, didn't test it really
int x = (event.getNativeEvent().getClientX() - target.getAbsoluteLeft()) + Window.getScrollLeft();
int y = (event.getNativeEvent().getClientY() - target.getAbsoluteTop()) + Window.getScrollTop();
target.getElement().getStyle().clearBackgroundColor();
Window.alert("x: " + x + ", y:" + y);
// add image with same URL as the dropped one to absolute panel at given coordinates
target.add(new Image(event.getData("text")), x, y);
}
});
RootPanel.get().add(image);
RootPanel.get().add(target);
}
And here the custom panel
public class DropAbsolutePanel extends AbsolutePanel implements HasDropHandlers, HasDragOverHandlers,
HasDragLeaveHandlers {
#Override
public HandlerRegistration addDropHandler(DropHandler handler) {
return addBitlessDomHandler(handler, DropEvent.getType());
}
#Override
public HandlerRegistration addDragOverHandler(DragOverHandler handler) {
return addBitlessDomHandler(handler, DragOverEvent.getType());
}
#Override
public HandlerRegistration addDragLeaveHandler(DragLeaveHandler handler) {
return addBitlessDomHandler(handler, DragLeaveEvent.getType());
}
}
and image class:
public class DragImage extends Image {
public DragImage() {
super();
initDnD();
}
private void initDnD() {
// enables dragging if browser supports html5
getElement().setDraggable(Element.DRAGGABLE_TRUE);
addDragStartHandler(new DragStartHandler() {
#Override
public void onDragStart(DragStartEvent event) {
// attach image URL to drag data
event.setData("text", getUrl());
}
});
}
}