Make scrollpane always on top - java

So, in my program, at the bottom, I've got a ScrollPane which has a TextFlow inside it to display a information in when the user clicks on items in a list. The ScrollPane was rather small, so I added an expand button which makes it larger so you can see more. But now, if there is a button or anything within the area that the expanded ScrollPane takes up, it is always on top of the pane and makes it hard to see anything. Is there a way I could force the ScrollPane to be on top and cover things? Attempting to change the z-index did nothing.
Should I just set anything within that area to be not visible while expanded instead?
This is what I'm doing to expand the ScrollPane:
Image expand_img = new Image(getClass().getResourceAsStream("expand.png"));
Button expand = new Button();
expand.setGraphic(new ImageView(expand_img));
expand.setLayoutX(630);
expand.setLayoutY(415);
expand.setStyle("-fx-background-color: transparent");
expand.addEventHandler(ActionEvent.ACTION, new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
++clicked;
if (clicked % 2 == 0){
scroll_desc.setTranslateY(-100);
scroll_desc.setPrefHeight(208);
expand.setLayoutY(315);
}else if (clicked % 2 == 1){
scroll_desc.setTranslateY(0);
scroll_desc.setPrefHeight(108);
expand.setLayoutY(415);
}
}
});
I just want it so that when the ScrollPane is larger, anything inside the new area is covered up by the ScrollPane rather than being overlayed on top and accessible, thus obscuring content of the descriptions and the like displayed.

Related

JavaFX: textArea.setScrollTop(Double.MAX_VALUE) not always works

This is the piece of my code.
textArea.setText(someNewText)
textArea.positionCaret(textArea.getText().length());
textArea.setEditable(true);
textArea.setScrollTop(Double.MAX_VALUE);
I use textArea.setScrollTop(Double.MAX_VALUE) to scroll textarea to the bottom (solution I found in internet). It works, but not always. I've noted that it can not work only when vertical scroll bar is not visible before calling this code and visible after the code was executed. When vertical scroll bar is visible before calling this code then scrolling to the bottom works always. How to fix it? Maybe I should make vertical scroll bar always visible? If yes, then how - I didn't find the solution.
EDIT:
This is the sample code:
public class JavaFxApp1 extends Application{
private TextArea textArea;
#Override
public void start(Stage stage) throws Exception {
Button button=new Button("Press here");
textArea=new TextArea();
VBox vbox = new VBox(button,textArea);
button.setOnAction((event)->{
textArea.appendText("###This is a very long string:some text some text some text some text some"
+ " text some text some text some text some text some text"
+ " text some text some text some text some text some text"
+ " text some text some text some text some text some text .\n");
textArea.selectEnd();
textArea.deselect();
textArea.setScrollTop(Double.MAX_VALUE);
});
textArea.setEditable(true);
textArea.setWrapText(true);
textArea.setStyle("-fx-font-size:14px;-fx-focus-color: transparent;-fx-font-family: monospace;");
Scene scene=new Scene(vbox);
stage.setTitle("SomeTitle");
stage.setScene(scene);
stage.setMinHeight(400);
stage.setMinWidth(800);
stage.show();
}
}
This is the result when I pressed button 4 times:
As you see it didn't scroll to the bottom. After I press button again (the fifth time) I have the following result:
Now, as you see it was scrolled to the bottom.
I tried to add:
ScrollPane scrollPane = (ScrollPane) textArea.lookup(".scroll-pane");
scrollPane.setVbarPolicy(ScrollBarPolicy.ALWAYS);
to make scrollbar visible always - it is visible but after 4 times anyway doesn't scroll to the bottom.
How to fix it?
if you want to scroll to the Top your code will be this way
ta.selectHome();
ta.deselect();
if you want to scroll to the Bottom your code will be this way
ta.selectEnd();
ta.deselect();
now let the TextArea worry about its visible areas
edit
maybe i didnt get your requirement, cause honestly this is the first time i have seen that method so to give an answer based on your hypothesis
Maybe I should make vertical scroll bar always visible? If yes, then how
well thats easy TextArea uses ScrollPane, when there is a visible scroll
this line TextArea.getChildrenUnmodifiable().size(); will check out to be 1, so you fish it out when you create your TextArea
TextArea.getChildrenUnmodifiable().addListener(new ListChangeListener<Node>() {
#Override
public void onChanged(javafx.collections.ListChangeListener.
Change<? extends Node> c) {
while(c.next()){
if(c.wasAdded()){
for(Node n : TextArea.getChildrenUnmodifiable()){
if(n.getClass().isAssignableFrom(ScrollPane.class)){
//just trying to be cool here ^^
ScrollPane sp = (ScrollPane) n;
sp.setVbarPolicy(ScrollBarPolicy.ALWAYS);
}
}
}
}
}
});
actually you can keep a reference to the ScrollPane and anytime you want to scroll to the bottom ScrollPane.setVvalue(1.0); to the top ScrollPane.setVvalue(0.0); etc etc.
you can get the value with ScrollPane.getVvalue().
This is a bug. See the following link https://bugs.openjdk.java.net/browse/JDK-8189732

Scaling a button's text automatically with the button in JavaFX

I made a grid of buttons in JavaFX.
When I resize the window with the grid inside, the buttons resize within the grid as well.
The problem is that the text on those buttons doesn't resize along with them: it stays the same size all the time, so when the buttons grow big, there's a lot of empty space on a button and then a tiny little text in the middle, which looks terrible.
I would rather like the text to automatically resize along with these buttons to fit the empty space, so that when the entire user interface gets bigger, the text on the buttons gets bigger as well.
How can I accomplish that?
I tried setting the -fx-font-size in the CSS stylesheet to percentage values, but it doesn't seem to work the same way as for websites: the text doesn't scale as a percentage of its container, but as a percentage of some predefined text size.
Edit
This is not a duplicate! Stop marking each question out there as duplicate! If it has been answered, I wouldn't have asked it in the first place!
From what I see, the first of those threads was about a situation where someone wanted to set the size/style of the text for newly-created buttons to account for the current size of their container etc. This is not what I need, because I want the buttons which has been already created as well to automatically resize their texts when these buttons resize inside their container in some way.
The other thread was about scaling the text along with the root container / window with a preset font size. This is also different from what I need, because I don't want the text to be scaled with the window, but with the sizes of the buttons themselves. And it has to be scaled in a certain way: to always fit the size of the button. You know: the text stays the same, but stretches so that it always fits the inside of the button (with a little padding, not a huge empty area around the text).
It is the button's size which is to determine the size of the text on it, not the window or container or something else, and it needs to be done automatically by the button itself (either the built-in one or a subclassed one), not manually by its encompassing container iterating over all these buttons and updating their text's sizes (which would be dumb way to do it).
This is, liked the linked questions, something of a hack: but consider scaling the text node inside the button instead of changing the font size. This seems to work ok:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class ScaledButtons extends Application {
#Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
root.setHgap(5);
root.setVgap(5);
for (int i = 1; i <= 9 ; i++) {
root.add(createScaledButton(Integer.toString(i)), (i-1) % 3, (i-1) / 3);
}
root.add(createScaledButton("#"), 0, 3);
root.add(createScaledButton("0"), 1, 3);
root.add(createScaledButton("*"), 2, 3);
primaryStage.setScene(new Scene(root, 250, 400));
primaryStage.show();
}
private Button createScaledButton(String text) {
Button button = new Button(text);
GridPane.setFillHeight(button, true);
GridPane.setFillWidth(button, true);
GridPane.setHgrow(button, Priority.ALWAYS);
GridPane.setVgrow(button, Priority.ALWAYS);
button.layoutBoundsProperty().addListener((obs, oldBounds, newBounds) ->
scaleButton(button));
button.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
return button ;
}
private void scaleButton(Button button) {
double w = button.getWidth();
double h = button.getHeight();
double bw = button.prefWidth(-1);
double bh = button.prefHeight(-1);
if (w == 0 || h == 0 || bw == 0 || bh == 0) return ;
double hScale = w / bw ;
double vScale = h / bw ;
double scale = Math.min(hScale, vScale);
button.lookup(".text").setScaleX(scale);
button.lookup(".text").setScaleY(scale);
}
public static void main(String[] args) {
launch(args);
}
}
An alternate approach to get a similar effect could be to subclass com.sun.javafx.scene.control.skin.ButtonSkin and override the layoutLabelInArea(double x, double y, double w, double h, Pos alignment) method from the skin's parent (LabeledSkinBase). You can then explicitly assign the updated skin to your button (either via CSS or via Java API calls).
Doing so would requires the subclassing of com.sun APIs which could change without notice in subsequent JavaFX releases. Also layoutLabelInArea is reasonably complex in its operation so changing the layout logic could be a little tricky. Certainly, James's suggestion of applying a text rescaling operation based upon a listener to the layout bounds property is simpler in this particular case.
I'm not necessarily advocating this approach, just providing a route to something that you could create that would satisfy your goal of: "It is the button's size which is to determine the size of the text on it, not the window or container or something else, and it needs to be done automatically by the button itself".

How to create a JButton with text on the button left and the icon separated from it on the button right

Basically, I'm trying to make a button that has the text aligned to the left (so I'm using setHorizontalAlignment(SwingConstants.LEFT)) and the image on the right border of the button, far from the text.
I already tried setHorizontalTextAlignment(SwingConstants.LEFT), but that just makes the text go relativity to the left of the icon, which is not exactly what I want, since I needed the icon to be secluded from it.
Also, I can't make any fixed spacing because it's a series of buttons with different texts with different sizes.
I can't make any fixed spacing because it's a series of buttons with different texts with different sizes.
You can dynamically change the spacing with code like:
JButton button = new JButton("Text on left:")
{
#Override
public void doLayout()
{
super.doLayout();
int preferredWidth = getPreferredSize().width;
int actualWidth = getSize().width;
if (actualWidth != preferredWidth)
{
int gap = getIconTextGap() + actualWidth - preferredWidth;
gap = Math.max(gap, UIManager.getInt("Button.iconTextGap"));
setIconTextGap(gap);
}
}
};
button.setIcon( new ImageIcon("copy16.gif") );
button.setHorizontalTextPosition(SwingConstants.LEADING);
This is a derivative of camickr's answer to allow editing in a GUI builder as well as placing it in a dynamic layout. I also removed the UIManager.getInt("Button.iconTextGap") so the gap will shrink to 0 if necessary.
I called it a 'Justified' button in analogy with justified text alignment (stretches a paragraph to left & right by growing width of space characters).
public class JustifiedButton extends JButton {
#Override
public void doLayout() {
super.doLayout();
setIconTextGap(0);
if (getHorizontalTextPosition() != CENTER) {
int newGap = getSize().width - getMinimumSize().width;
if (newGap > 0)
setIconTextGap(newGap);
}
}
#Override
public Dimension getMinimumSize() {
Dimension minimumSize = super.getMinimumSize();
if (getHorizontalTextPosition() != CENTER)
minimumSize.width -= getIconTextGap();
return minimumSize;
}
#Override
public Dimension getPreferredSize() {
Dimension preferredSize = super.getPreferredSize();
if (getHorizontalTextPosition() != CENTER)
preferredSize.width -= getIconTextGap();
return preferredSize;
}
}
This is not exactly production-ready and needs some field-testing. If I find anything, I'll edit the code.
[edit] Now works for vertical text alignments. Also simplified a bit.
[edit2] Also manipulate getPreferredSize to play nice with scroll pane (otherwise it keeps growing and never shrinks again)
You can add a layout manager to your button.
JButton btn = new JButton();
btn.add(new JLabel(text));
btn.add(new JLabel(img));
btn.setLayout(/*best layout choice here*/);
btn.setPreferredSize(new Dimension(x,y));
btn.setMaximumSize(new Dimension(maxX, minY));
btn.setMinimumSize(new Dimension(minX, minY)); //this one is most important when it comes to layoutmanagers
Sorry I can't be much help when it comes to picking out a good layout - But this will eventually get you what you want. Maybe someone else can comment on which one to use.

JTable#scrollRectToVisible in combination with JSplitPlane shows the wrong row

When I call JTable#scrollRectToVisible, the row I want to show is hidden underneath the header in certain situations.
The rest of this question only makes sense when using the following code. This is a very simply program which I use to illustrate the problem. It shows a UI containing a JSplitPane with in the upper part some control buttons, and the lower part contains a JTable wrapped in a JScrollPane (see screenshots at the bottom of this post).
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
public class DividerTest {
private final JSplitPane fSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
private final JTable fTable;
private final JScrollPane fScrollPane;
private boolean fHideTable = false;
public DividerTest() {
fTable = new JTable( createTableModel(50));
fScrollPane = new JScrollPane(fTable);
fSplitPane.setBottomComponent(fScrollPane);
fSplitPane.setTopComponent(createControlsPanel());
fSplitPane.setDividerLocation(0.5);
}
private JPanel createControlsPanel(){
JPanel result = new JPanel();
result.setLayout(new BoxLayout(result, BoxLayout.PAGE_AXIS));
final JCheckBox checkBox = new JCheckBox("Make table invisible before adjusting divider");
checkBox.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
fHideTable = checkBox.isSelected();
}
});
result.add(checkBox);
JButton upperRow = new JButton("Select row 10");
upperRow.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
selectRowInTableAndScroll(10);
}
});
result.add(upperRow);
JButton lowerRow = new JButton("Select row 45");
lowerRow.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
selectRowInTableAndScroll(45);
}
});
result.add(lowerRow);
JButton hideBottom = new JButton("Hide bottom");
hideBottom.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (fHideTable) {
fScrollPane.setVisible(false);
}
fSplitPane.setDividerLocation(1.0);
}
});
result.add(hideBottom);
JButton showBottom = new JButton("Show bottom");
showBottom.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fScrollPane.setVisible(true);
fSplitPane.setDividerLocation(0.5);
}
});
result.add(showBottom);
return result;
}
private void selectRowInTableAndScroll( int aRowIndex ){
fTable.clearSelection();
fTable.getSelectionModel().addSelectionInterval(aRowIndex, aRowIndex);
fTable.scrollRectToVisible(fTable.getCellRect(aRowIndex, 0, true));
}
public JComponent getUI(){
return fSplitPane;
}
private TableModel createTableModel(int aNumberOfRows){
Object[][] data = new Object[aNumberOfRows][1];
for( int i = 0; i < aNumberOfRows; i++ ){
data[i] = new String[]{"Row" + i};
}
return new DefaultTableModel(data, new String[]{"Column"});
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Test frame");
frame.getContentPane().add(new DividerTest().getUI());
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
});
}
}
Unwanted behavior
Run the above code
Press the "Select row 10": row 10 is selected and visible
Press the "Select row 45": row 45 is selected and visible
Click the "Hide bottom" button. This will adjust the divider of the JSplitPane so that only the upper panel is visible
Click the "Select row 10" button. You see of course nothing because the table is not yet visible
Click the "Show bottom" button. The divider is adjusted, but row 10 is hidden underneath the header. I expected it to be visible without needing to scroll.
Wanted behavior
Repeat the steps from above, but make sure the "Make table invisible before adjusting divider" checkbox is selected. This will call setVisible(false) on the JScrollPane around the JTable before hiding the bottom panel.
By doing this, in the last step row 10 will be visible as the top most row, which is what I want. I just do not want to turn the scrollpane invisible: in my real application, the divider is adjusted in an animated way and as such you want to keep the table visible during the animation.
Screenshots
Unwanted: row 10 is invisible after performing the aforementioned steps
Wanted: row 10 is visible after performing the aforementioned steps
Environment
I do not think it will matter, but just in case: I am using JDK7 on a Linux system.
This seems to be caused by the way how the JViewport handles the scrollRectToVisible calls for the cases that its size is smaller than the desired rectangle. It contains a (somewhat fuzzy, but probably related) comment in the JavaDocs:
Note that this method will not scroll outside of the valid viewport; for example, if contentRect is larger than the viewport, scrolling will be confined to the viewport's bounds.
I did not go though the complete code and do all the maths and check all the cases. So a warning: The following explainations contain quite same hand-waving. But a simplified description of what this means for me in this particular case:
When the bottom part is hidden (by setting the divider location accordingly), then this height of the JScrollPane and its JViewport is 0. Now, when requesting to scrollRectToVisible with a rectangle that has a height of 20 (for one table row, as an example), then it will notice that this does not fit. Depending on the current view position of the JViewport, this may cause to viewport to be scrolled so that the bottom of this rectangle is visible.
(You can observe this: Drag the divider location manually, so that approximately half of one table row is visible. When clicking the "Select row 45" button, the upper half of the row will be visible. When clicking the "Select row 10" button, then the lower half of the row will be visible)
One pragmatic solution here that seemed to work for me was to make sure that it will always scroll so that the top of the rectangle is visible (even when the rectangle does not at all fit into the viewport!). Like this:
private void selectRowInTableAndScroll(int aRowIndex)
{
fTable.clearSelection();
fTable.getSelectionModel().addSelectionInterval(aRowIndex, aRowIndex);
Rectangle r = fTable.getCellRect(aRowIndex, 0, true);
r.height = Math.min(fScrollPane.getHeight(), r.height);
fTable.scrollRectToVisible(r);
}
But I can't promise that this will have the desired effect for you, when an animation comes into play...
Not exactly sure what the scrollRectToVisible() is doing.
You might be able to use the JViewport.setViewPosition(...) method.
Rectangle r = fTable.getCellRect(aRowIndex, 0, true);
Point p = new Point(r.x, r.y);
fScrollPane.getViewport().setViewPosition( p );
In this case the selected row will always be shown at the top of the viewport (if possible). So the viewport will always scroll unless the selected row is current at the top. Using this approach if the first row is at the top of the viewport and you select the 10th row the viewport will scroll to display the 10th row at the top.
However, this behaviour is slightly different than using the scrollRectToVisible() method. When using the scrollRectToVisible() method the viewport when only scrolled when the rectangle is not in the visible part of the viewport. Using this approach if the first row is at the top of the viewport and you select the 10th row the viewport will NOT scroll since the 10th row is already visible in the viewport.
Don't know if this change in functionality is acceptable or not.
Note if you don't want to viewport to automatically scroll when you select a row you could try something like:
JViewport viewport = fScrollPane.getViewport();
Rectangle viewRect = viewport.getViewRect();
Rectangle r = fTable.getCellRect(aRowIndex, 0, true);
Point p = new Point(r.x, r.y);
if (! viewRect.contains(p))
viewport.setViewPosition( p );

Swing: Positioning a popup from within a JScrollPane

I have a JTable inside of a JScrollPane. I am creating a custom cell editor for one of the columns of the table, and I want this editor to pop up a scrolling JList. I've done this by using a Popup to show a new JScrollPane containing the JList.
Everything is working, except for the position of the Popup. My custom component for the editor looks basically like this:
public class CustomPanel extends JPanel {
JTextField text = new JTextField();
JList list = new JList();
JScrollPane scroll = new JScrollPane(list);
Component owner = null;
public CustomPanel(Component owner) {
this.owner = owner;
add(text);
}
public void showPopup() {
Popup p = PopupFactory.getPopup(owner, scroll, getX(), getY()+getHeight());
p.show();
}
}
What is happening is that getX() and getY() are returning the position of the table cell relative to the JScrollPane holding it, and Popup is wanting absolute screen position. Even if I pass in owner the JScrollPane that they are relative to, it doesn't work. I get the same problem if I use text.getX() / text.getY().
How can I position my Popup directly below the TextBox?
Just a bit more background: The end goal is a multiple-select combobox that displays all of the selected items as a comma-separated list. If something else like this already exists, please don't hesitate to point me to it.
Edit: owner.getLocationOnScreen().y + getY() doesn't work when the scroll pane is anywhere but scrolled all the way up. However, just plain getLocationOnScreen().y DOES work. Problem solved, thank you.
You can query the absolute screen position with Component.getLocationOnScreen(). Is that what you're looking for?

Categories

Resources