In this minimal example we can drag the Tabs to reorder them:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.stage.Stage;
public class MainApp extends Application {
#Override
public void start(Stage stage) throws Exception {
TabPane tabPane = new TabPane();
tabPane.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
Tab tab = new Tab("Tab");
tab.setGraphic(new Button("button"));
Tab tab2 = new Tab("Tab");
tab2.setGraphic(new Button("button"));
tabPane.getTabs().addAll(tab, tab2);
Scene scene = new Scene(tabPane);
tabPane.setPrefWidth(600);
tabPane.setPrefHeight(400);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
We can start a drag when the mouse is over the tabs name, but not when it is over the button (the tabs graphic). The button in this case is blocking the drag event from being seen by the tab.
I cannot simply set isMouseTransparant as that prevents the button from registering its own mouse clicks.
How can I allow the user to drag a tab through the tabs graphic while still being able to interact with the graphic via clicking?
Example mode based solution
I suggest implementing a reorder mode.
When the reorder mode is switched on, the nodes in the graphics of the tab headers are all disabled and the user can click anywhere in the tab headers to drag and reorder the associated tabs. During this time, the user cannot interact with the disabled graphic nodes.
When the reorder mode is switched off, the nodes in the graphics of the tab headers are all enabled. The user cannot drag the tabs to reorder them, but they can interact with the nodes in the graphics to perform their functions.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.Objects;
public class MainApp extends Application {
#Override
public void start(Stage stage) throws Exception {
TabPane tabPane = new TabPane();
Tab tab = new Tab("Tab");
tab.setGraphic(new Button("button"));
Tab tab2 = new Tab("Tab");
tab2.setGraphic(new Button("button"));
tabPane.getTabs().addAll(tab, tab2);
ToggleButton reorderModeToggle = new ToggleButton("Reorder Mode");
reorderModeToggle.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
tabPane.getTabs().stream()
.map(Tab::getGraphic)
.filter(Objects::nonNull)
.forEach(graphic -> graphic.setDisable(isSelected));
tabPane.setTabDragPolicy(
isSelected
? TabPane.TabDragPolicy.REORDER
: TabPane.TabDragPolicy.FIXED
);
});
VBox layout = new VBox(10, reorderModeToggle, tabPane);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
tabPane.setPrefWidth(600);
tabPane.setPrefHeight(400);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If new tabs can be added dynamically by the user, then you could additionally appropriately initialize their disable state based on the reorder mode (or you could do that in a list change listener for the tabs in the TabPane). I didn't provide logic for this additional optional extra step.
Discussion of why this approach is provided
I realize that is not what you originally asked for, but it might be a reasonable solution to your problem.
Trying to make it so that the graphics control work and the drag functions of the tab are also available at the same time to appear (to me) to be quite problematic, both in terms of how to implement this and the behavior that the user should expect to occur. For example, a button will be fired when the mouse is pressed, but a drag event for a tab header will also begin when a mouse is pressed, so, if you press a button in the header, should you also begin the drag event? It is quite unclear.
The input handling for buttons is handled by an InputMap class inside a ButtonBehavior reference by the ButtonSkin. Both of which are non-public APIs, so trying to override the default behavior is difficult.
There are methods in the private InputMap class to help decide whether the behavior of the button should consume the event it takes actions on (a mouse press or key press) or not (by default it will). So this could be done by installing your own skin replicating (a lot) of the functionality in the private API with some modifications to provide the custom functionality you want (but I do not recommend this at all).
I had thought that using event filters you would be able to provide the original functionality you requested, and you may be able to do that, but from my investigations, that would not be at all easy.
Alternate Discussion
The alternate solution suggested by sorifiend in comments might also work for you:
... option is to not use a button. Try with a label instead (Labels don't capture any events by default), or you could use custom graphics to render the tab header to look however you want, and then it will behave exactly as you want without a button.
Alternately, instead of putting interactive graphics in the headers, you could place all your interactive nodes body of each tab, keeping only text or static images in the headers, allowing them to always be re-orderable.
Related
In this minimal example we can drag the Tabs to reorder them:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.stage.Stage;
public class MainApp extends Application {
#Override
public void start(Stage stage) throws Exception {
TabPane tabPane = new TabPane();
tabPane.setTabDragPolicy(TabPane.TabDragPolicy.REORDER);
Tab tab = new Tab("Tab");
tab.setGraphic(new Button("button"));
Tab tab2 = new Tab("Tab");
tab2.setGraphic(new Button("button"));
tabPane.getTabs().addAll(tab, tab2);
Scene scene = new Scene(tabPane);
tabPane.setPrefWidth(600);
tabPane.setPrefHeight(400);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
We can start a drag when the mouse is over the tabs name, but not when it is over the button (the tabs graphic). The button in this case is blocking the drag event from being seen by the tab.
I cannot simply set isMouseTransparant as that prevents the button from registering its own mouse clicks.
How can I allow the user to drag a tab through the tabs graphic while still being able to interact with the graphic via clicking?
Example mode based solution
I suggest implementing a reorder mode.
When the reorder mode is switched on, the nodes in the graphics of the tab headers are all disabled and the user can click anywhere in the tab headers to drag and reorder the associated tabs. During this time, the user cannot interact with the disabled graphic nodes.
When the reorder mode is switched off, the nodes in the graphics of the tab headers are all enabled. The user cannot drag the tabs to reorder them, but they can interact with the nodes in the graphics to perform their functions.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.Objects;
public class MainApp extends Application {
#Override
public void start(Stage stage) throws Exception {
TabPane tabPane = new TabPane();
Tab tab = new Tab("Tab");
tab.setGraphic(new Button("button"));
Tab tab2 = new Tab("Tab");
tab2.setGraphic(new Button("button"));
tabPane.getTabs().addAll(tab, tab2);
ToggleButton reorderModeToggle = new ToggleButton("Reorder Mode");
reorderModeToggle.selectedProperty().addListener((observable, wasSelected, isSelected) -> {
tabPane.getTabs().stream()
.map(Tab::getGraphic)
.filter(Objects::nonNull)
.forEach(graphic -> graphic.setDisable(isSelected));
tabPane.setTabDragPolicy(
isSelected
? TabPane.TabDragPolicy.REORDER
: TabPane.TabDragPolicy.FIXED
);
});
VBox layout = new VBox(10, reorderModeToggle, tabPane);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
tabPane.setPrefWidth(600);
tabPane.setPrefHeight(400);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
If new tabs can be added dynamically by the user, then you could additionally appropriately initialize their disable state based on the reorder mode (or you could do that in a list change listener for the tabs in the TabPane). I didn't provide logic for this additional optional extra step.
Discussion of why this approach is provided
I realize that is not what you originally asked for, but it might be a reasonable solution to your problem.
Trying to make it so that the graphics control work and the drag functions of the tab are also available at the same time to appear (to me) to be quite problematic, both in terms of how to implement this and the behavior that the user should expect to occur. For example, a button will be fired when the mouse is pressed, but a drag event for a tab header will also begin when a mouse is pressed, so, if you press a button in the header, should you also begin the drag event? It is quite unclear.
The input handling for buttons is handled by an InputMap class inside a ButtonBehavior reference by the ButtonSkin. Both of which are non-public APIs, so trying to override the default behavior is difficult.
There are methods in the private InputMap class to help decide whether the behavior of the button should consume the event it takes actions on (a mouse press or key press) or not (by default it will). So this could be done by installing your own skin replicating (a lot) of the functionality in the private API with some modifications to provide the custom functionality you want (but I do not recommend this at all).
I had thought that using event filters you would be able to provide the original functionality you requested, and you may be able to do that, but from my investigations, that would not be at all easy.
Alternate Discussion
The alternate solution suggested by sorifiend in comments might also work for you:
... option is to not use a button. Try with a label instead (Labels don't capture any events by default), or you could use custom graphics to render the tab header to look however you want, and then it will behave exactly as you want without a button.
Alternately, instead of putting interactive graphics in the headers, you could place all your interactive nodes body of each tab, keeping only text or static images in the headers, allowing them to always be re-orderable.
Ok so to make a long story short I'm trying to create a sort of chat/message system and need a small bit of assistance. I'm trying to create an arrow on my container as the one shown in the image below. The image is taken off of ControlsFX and from their PopOver window. I CANNOT use their popover widget because it behaves a little wonky with what I'm using it for.
I went ahead and created my own little chat window popup that positions itself over the parent object that I define but I would really like it to have an arrow pointing to the object. The arrow will also ALWAYS face down and should be in the lower left-hand corner of the popup.
It should also be noted that the popup is NOT a window its a simple VBox that populates with lines of text. I could of course wrap that in a Pane if its needed. Can anyone come up with the proper way to create this arrow? I have my VBox background as a gradient as well so the arrow can't just be like plopped on the bottom through getChildren().add with the "same color" because then the gradient would be off. It has to be (somehow) a part of the container.
===========================================================================
EDIT:
Alright so I spent the greater part of today learning SVG Pathing which isn't too awful complicated but it's slightly tedious. The path I ended up going with is:
"M30 0 h100 a6,6 0 0,1 6,6 v50 a6,6 0 0,1 -6,6 h-88 L38 68 L34 62 h-4 a6,6 0
0,1 -6,-6 v-50 a6,6 0 0,1 6,-6 z"
Now the only problem with this is the arrow tail height grows with the size of the pane. For instance if I have a LOT of text in the box the pane grows in height (of course) and the arrow gets longer too. This behavior isn't a total deal breaker but it's not really what I had intended. I expected the capital Ls in the pathing to make sure that the points of the arrow stay put no matter what but it didn't work. Any thoughts on this?
There are a number of ways to achieve the effect which you wish.
One way is to use a CSS -fx-shape attribute specification on a region. See the JavaFX CSS reference for info on this setting and how to use it.
The sample below uses inline styles, but for a more maintainable and substantial application use an external CSS stylesheet.
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.effect.DropShadow;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Bubble extends Application {
private static final String SQUARE_BUBBLE =
"M24 1h-24v16.981h4v5.019l7-5.019h13z";
// source for svg path string: https://iconmonstr.com/speech-bubble-7/
private static final String ROUND_BUBBLE =
"M12 1c-6.628 0-12 4.573-12 10.213 0 2.39.932 4.591 2.427 6.164l-2.427 5.623 7.563-2.26c9.495 2.598 16.437-3.251 16.437-9.527 0-5.64-5.372-10.213-12-10.213z";
#Override
public void start(Stage stage) {
Label label = new Label("hello, world");
label.setStyle("-fx-font-size: 16px;");
StackPane bubble = new StackPane(label);
bubble.setPadding(new Insets(20));
bubble.setStyle(
"-fx-background-color: lightblue; " +
"-fx-border-color: navy; -fx-border-width: 2px; " +
"-fx-shape: \"" + ROUND_BUBBLE + "\";"
);
bubble.setEffect(new DropShadow(10, 5, 5, Color.MIDNIGHTBLUE));
StackPane layout = new StackPane(bubble);
layout.setPadding(new Insets(20));
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I realize that the bubble shape demoed in this application isn't exactly the shape which you want but I don't understand from your description what the shape you want is. There are numerous bubble shapes already created at iconmonstr.com, you can just use its interface to search for speech bubbles and select the shape you want, or you can define your own svg path for your custom shape if you have the tools (e.g. Inkscape) and skill for that. To extract the svg path from an existing svg file, open up the svg file, hope that it is encoded in a compact path string format, and if it is, just copy and paste the path part from the svg into your JavaFX css file.
Another way to do this would be to programmatically construct a path which you would layer with the content by placing both in a StackPane.
I recently asked a similar question about this for OSX and found a solution using the com.sun.glass package. However, this solution does not seem to work on X11-based desktop environments.
The issue:
I am trying to write a borderless window overlay that can be placed above ALL other desktop components, including the dock and menubar of any given Linux desktop environment which uses x11. Currently, when I use AlwaysOnTop(true) with my JavaFX window, the window is placed above all other windows but is NOT placed above the window managers UI (taskbar) due to it having a higher window level. The screenshot below shows what happens when using AlwaysOnTop: the entirety of the vertical white window should be placed above the taskbar but is instead forced below it. See screenshot:
There is a solution for this issue with Qt through using the x11bypasswindowmanager window flag, so I figured it must be possible through Java!
The only current solution I have is to use wmctrl directly through the commandline using a subprocess. This is not really suitable, as a lot of linux systems to not come with wmctrl installed.
Below is the code snippet I am using to generate the window in the above screenshot:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World!");
StackPane root = new StackPane();
primaryStage.setScene(new Scene(root, 200, 3000));
primaryStage.setAlwaysOnTop(true);
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setX(800);
primaryStage.setY(0);
primaryStage.show();
}
}
It says on the OpenJDK samples for TextFlow that it can have arbitrary Nodes as children, e.g. Button. I was wondering if WebView can be a possible child? If so, then I must be doing something wrong. When I add it to the TextFlow, the WebView just takes up space, but displays nothing. The content I've tried range from simple HTML pages, up to videos from services like YouTube.
Since this control is fairly new, I was unable to find an existing use case online.
In case you're wondering why I want this. I have a ListView that displays rich content. If TextFlow is capable of displaying any Node, then it would save me from creating multiple extension classes for the different types I want to display on the ListView (e.g. Video, formatted text, HTML - these I already did, and they work fine), and I can manage content easily by just managing the TextFlow's children.
Thanks.
Yes, you can. Note that the web view will be layout out using its baselineOffset, which is not going to be appropriate for use in a TextFlow (I think what happens is it's bottom edge will be aligned with the bottom edge of the text, and it gets clipped to the height of the text row.) You can get around this by wrapping it in a Group:
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class WebViewInTextFlow extends Application {
#Override
public void start(Stage primaryStage) {
TextFlow flow = new TextFlow();
WebView webView = new WebView();
webView.getEngine().getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> System.out.println(newState));
webView.getEngine().load("http://stackoverflow.com/questions/31353921/is-webview-embeddable-in-textflow/31357609#31357609");
flow.getChildren().addAll(new Text("Some text"), new Group(webView), new Text("Some more text"));
primaryStage.setScene(new Scene(flow, 800, 800));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I am making a Mac application, and I want my menu bar to look right.
Any Mac user knows the menu bar should be in the top screen menu. Setting apple.laf.useScreenMenuBar to true in the property list file gets rid of the in-frame menu bars and moves the menu bar of the current focused window to the screen menu.
However, when all windows are hidden or when there are no windows, there are no menu bars to move to the top, and you just get a blank menu. I heard a solution to this was to create an offscreen window that is focused when no others are. The only purpose of it would be its menu, so that it could fill in when the others are gone.
However, I've been getting loads of problems. I can't seem to move the window off the screen because Macs won't let you set the coordinates to something past the size of the screen; it just cuts it off and positions it at the edge instead. Is there something else I have to do to make an offscreen window?
You should definitely consider WizardOfOdds' very helpful answer. Using "The Application Menu" correctly will help, and it's easy to set up a minimal Info.plist to get started. A persistent File menu will allow your application to open a new window when others are closed. This answer links to a simple example.
Although Apple's Human Interface Guidelines are an excellent guide to what your users will expect, you can certainly experiment with the approach you suggested in your question. In particular, you might try setLocation(Short.MIN_VALUE, Short.MIN_VALUE) on the invisible window. In addition, you might want to respond to a WindowEvent in some special way if it signals the close of the last visible window.
Addendum: When your listener sees the last visible window close, create a new, empty application window. Alternatively, move the invisible window onscreen and make it visible until the user decides how to proceed.
Addendum: Mac OS X helpfully prevents a visible window form being moved offscreen, but it's easy to put an invisible window in limbo, as shown below.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
public class FrameTest extends JFrame {
private static FrameTest marco;
private static FrameTest polo;
private static class MyPanel extends JPanel {
public MyPanel() {
super(true);
final JToggleButton b = new JToggleButton("Test");
b.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
if (b.isSelected()) {
polo.setLocation(100, 100);
polo.setVisible(true);
}
else {
polo.setVisible(false);
polo.setLocation(Short.MIN_VALUE, Short.MIN_VALUE);
}
}
});
this.add(b);
}
}
public FrameTest(String title) {
super(title);
this.setLayout(new BorderLayout());
this.add(new MyPanel());
this.pack();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(final String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
marco = new FrameTest("Marco");
marco.setLocationRelativeTo(null);
marco.setVisible(true);
polo = new FrameTest("Polo");
polo.setLocation(Short.MIN_VALUE, Short.MIN_VALUE);
}
});
}
}
I know that this post is quite old, anyway, I had the same problem and found the solution. Actually it's quite simple. Just don't add the JMenuBar to your main frame when running on mac os x, but to your application using
com.apple.eawt.Application.getApplication().setDefaultMenuBar(menuBar);
Now the MenuBar is still displayed even if you set all frame's visibility to false.
Not a direct solution, but I think some create a 1-pixel window instead. That yields complaints though, like one described at Super User: Chaotic behavior of a dead pixel on my iMac 24"...
First a note: your question seems really to be "How to have a Window menu following the Apple Human Interface Guidelines" and not "creating an offscreen frame in Java", which seems like a monstrous hack.
I suggest checking Apple's "Mac OS X Integration for Java", which, under "Window menu", shows apparently exactly what you're trying to achieve:
Apple Human Interface Guidelines suggests that all Mac OS X applications should provide a Window menu to keep track of all currently open windows. A Window menu should contain a list of windows, with a checkmark next to the active window.
This is a comment to trashgod's answer. It's too big so I have to move it here >.<
More problems!
I'm getting 2 errors:
line 23: The method itemStateChanged(ItemEvent) of type new ItemListener(){} must override a superclass method
line 50:The method run() of type new Runnable(){} must override a superclass method
Any help? I've never encountered this before. I don't know what it means.
EDIT: I have more problems! I need the offscreen window to be visible while it is offscreen for it to produce a menu to move to the screen menu bar. When I use the code
offScreen = new JFrame("WTF?! You can see me?");
offScreen.setSize(400,300);
offScreen.setLocation(0, java.awt.Toolkit.getDefaultToolkit().getScreenSize().height+50);
System.out.println(offScreen.getLocation());
offScreen.setVisible(true);
System.out.println(offScreen.getLocation());
I get the output:
java.awt.Point[x=0,y=1100]
java.awt.Point[x=0,y=961]
It moves it back once it's made visible again!
I've searched for ages and I can't find anything.
This code works in Java 7:
if( isMac ) {
//This creates an invisible frame so that we always have a menu bar visible
JFrame menuFrame = new JFrame();
menuFrame.setUndecorated( true );
menuFrame.setJMenuBar( defaultMenuBar );
AWTUtilities.setWindowOpaque( menuFrame, false );
menuFrame.setBounds( 0,0,1,1 );
menuFrame.setVisible( true );
}
Just call this before you open any other windows, and it will stay in the background and automatically become the focused window when others are closed. You can still use the com.apple.eawt.Application.getApplication().setDefaultMenuBar(menuBar) method in your application so that you don't need to call setJMenuBar() on each JFrame.