I have a JTable with a few columns and potentially very many rows. I only want to display 3 rows at a time, and I want the width of the JTable to be 400, including the width of the scroll bar.
Here is the code I have so far:
public class SwingDrivesMeMad
{
public static void main(String[] args)
{
Object[][] rowData = { {"Homer", "beer"},
{"Marge", "wine"},
{"Bart", "coke"},
{"Lisa", "water"},
{"Maggie", "milk"} };
Object[] headerData = { "name", "beverage" };
JTable table = new JTable(rowData, headerData)
{
public Dimension getPreferredScrollableViewportSize()
{
return new Dimension(400, getRowHeight() * 3);
}
};
JScrollPane scrollPane = new JScrollPane(table);
JFrame frame = new JFrame("learn you a barkeep for great good!");
frame.add(scrollPane);
frame.pack();
frame.setVisible(true);
}
}
This is how it looks:
The height is perfect. Unfortunately, the width is 400 plus the width of the scrollbar, with a total of 415. This messes up the layout, because there is a graphics area with a fixed width of 400 directly above the table, and both components should line up perfectly. Having the table be slightly bigger just looks bad.
Of course I could simply set the width to 385, because on my system, scrollbars appear to take 15 pixels, but that does not seem very portable. Is there a better solution?
Unfortunately, none of the proposed solutions were pixel-perfect. So I came up with a hack:
How about trying a width of 400 first, then calculate the actual width, and finally correct for the excess?
JTable table = new JTable(rowData, headerData)
{
private static final int DESIRED_WIDTH = 400;
Dimension dim = new Dimension(DESIRED_WIDTH, Integer.MAX_VALUE);
{
JScrollPane dummy = new JScrollPane(this);
dummy.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
JFrame frame = new JFrame();
frame.add(dummy);
frame.pack();
int actualWidth = dummy.getSize().width;
frame.dispose();
int excess = actualWidth - DESIRED_WIDTH;
dim = new Dimension(DESIRED_WIDTH - excess, getRowHeight() * 3);
}
public Dimension getPreferredScrollableViewportSize()
{
return dim;
}
};
Now scrollPane.getSize().width is exactly 400. Yes, it's a hack. But hey, it works!
Related
I have a JTextArea with a long text. In the text there is the string "abc" that I want to be displayed in the middle (vertical) of the text area.
I can use setCaretPosition to cause the ScrollPane to scroll enough for the string "abc" to scroll into view. The scrolling continues until the text "abc" becomes visible at the bottom of the text area (see example)
How can I get the scrollbar to scroll so far that the text "abc" is displayed in the middle (vertically) in the visible area?
many thanks for your help!
public class TestCenter {
public TestCenter() {
JFrame frame = new JFrame();
JTextArea ta = new JTextArea();
frame.setMinimumSize(new Dimension(800, 600));
frame.add(new JScrollPane(ta));
frame.pack();
frame.setVisible(true);
SwingUtilities.invokeLater(() -> {
StringBuilder testText = new StringBuilder();
for (int t = 0; t < 100; t++) {
testText.append("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
}
testText.append("abc");
for (int t = 0; t < 100; t++) {
testText.append("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
}
ta.setText(testText.toString());
ta.setCaretPosition(testText.indexOf("abc"));
});
}
public static void main(String[] args) {
new TestCenter();
}
}
To move the scroll bar to the position of the abc text to be displayed vertically in the middle of the viewport we have to first find the line number of the text and translate it to the view coordinates:
// Find the line number of the "abc" text
int lineNumber = ta.getLineOfOffset(testText.indexOf("abc"));
// Translate the lineNumber to the view rectangle
Rectangle viewRect = ta.modelToView(ta.getLineStartOffset(lineNumber));
Now, having the viewRect we can get the viewport size and calculate the position Point to scroll to
Dimension viewportSize = scrollPane.getViewport().getExtentSize();
// Calculate new view position to scroll to
Point scrollPoint = new Point(0, view.y - (viewportSize.height - view.height) / 2);
Some explanation about calculating the scrollPoint. To calculate the y-coordinate at which the line should be centered, we use the following formula: rect.y - (viewportSize.height - viewRect.height) / 2.
We subtract the height of the line (viewRect.height) from the total viewport size (viewportSize.height) which gives the remaining space in the viewport above and below the line. Then we divide the remaining space by 2 to get the position of the line in the center of viewport.
And as we want to vertically center the line in the viewport, we subtract the resulting value from the top y-coordinate of the line to get the new y-coordinate of the viewport.
Finally, we can set the viewport to the calculated scrollPoint using JViewport::setViewPosition(Point):
public TestCenter() {
JFrame frame = new JFrame();
JTextArea ta = new JTextArea();
JScrollPane scrollPane = new JScrollPane(ta);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setMinimumSize(new Dimension(800, 600));
frame.add(scrollPane);
frame.pack();
frame.setVisible(true);
var testText =
"xxxxxxxxxxxxxxxxxxxx\n".repeat(100) +
"abc" +
"xxxxxxxxxxxxxxxxxxxx\n".repeat(100);
ta.setText(testText.toString());
try {
// Find the line number of the "abc" text
int lineNumber = ta.getLineOfOffset(testText.indexOf("abc"));
// Translate the lineNumber to the view rectangle
Rectangle viewRect = ta.modelToView(ta.getLineStartOffset(lineNumber));
// Get the size of the viewport
Dimension viewportSize = scrollPane.getViewport().getExtentSize();
// Calculate new view position to scroll to
Point scrollPoint = new Point(0, viewRect.y - (viewportSize.height - viewRect.height) / 2);
// Move the view to the new position
scrollPane.getViewport().setViewPosition(scrollPoint);
} catch (BadLocationException e) {
throw new RuntimeException(e);
}
}
JTextArea has a setColumns method to set maximum number of columns, but JTextPane doesn't. Of course, it can have different fonts in a single pane, so setting number of columns or rows doesn't exactly make sense. However, when I create a dialog containing a JTextPane inside a JScrollPane and setText to a long string, it grows to the entire width of the screen and a single row, which I want to prevent.
setMaximumSize doesn't seem to have an effect and is not recommended at any rate (see Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?).
My current solution, which seems quite ugly, is to extend JTextPane, borrowing code from JTextArea implementation:
textArea = new JTextPane() {
int maxWidth;
int rowHeight;
void init(Font font) {
FontMetrics fontMetrics = getFontMetrics(font);
maxWidth = fontMetrics.charWidth('M') * 80;
rowHeight = fontMetrics.getHeight();
}
{
initFont(getFont());
}
#Override
public void setFont(Font font) {
init(font);
super.setFont(font);
}
#Override
public Dimension getPreferredScrollableViewportSize() {
Dimension base = super.getPreferredSize();
Insets insets = getInsets();
int width = Math.min(maxWidth, base.width) + insets.left + insets.right;
int estimatedRows = Math.max(1, (int) Math.ceil(base.getWidth() / maxWidth));
int height = estimatedRows * rowHeight + insets.top + insets.bottom;
return new Dimension(width, height);
}
};
I'm trying to make a level editor for my platformer game, I want my levels to be 100 by 100 squares.
So far the editor works, but I can't scroll through the JPanel. I've been playing around and I've made a small test class to fiddle with which I'll post. If you run it, all it does it show the grid. However if I swap out two variables (I'll comment where) it can show an image and scroll according to the size of that image.
I want that scrolling ability only for the JPanel, so that I can scroll through my 100 x 100 square level.
import java.awt.BorderLayout;
public class ScrollPaneJ extends JFrame {
// setting the panels
private JPanel contentPane;
private JScrollPane scrollPane;
// dimensions/ variables of the grid
int size = 16;
int startX = 112;
int startY = 48;
int width = 30;
int height = 30;
// this is the grid
String[][] grid = new String[width][height];
// this is from the full editor class
String currentImage = new String("platform");
ImageIcon currentBackIcon = new ImageIcon("Resources/backdirttile.jpg");
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
// adding the scrollpane
ScrollPaneJ frame = new ScrollPaneJ();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public ScrollPaneJ() {
setTitle("Scrolling Pane Application");
setSize(new Dimension(300, 200));
setBackground(Color.gray);
setDefaultCloseOperation(EXIT_ON_CLOSE);
// defining the top and bottom panels, bottom is what I think I'm
// drawing on, top is where the scrollpanel goes, I copied this code
// from the internet and I'm not too sure how it works
JPanel topPanel = new JPanel();
JPanel bottomPanel = new JPanel(new GridLayout());
bottomPanel.setLayout(new BorderLayout());
getContentPane().add(bottomPanel);
topPanel.setLayout(new BorderLayout());
getContentPane().add(topPanel);
// this is the label I was talking about
Icon image = new ImageIcon("src/MenuDesign.jpg");
JLabel label = new JLabel(image);
// Create a tabbed pane
// if you set it to say label instead of bottomPanel, you can scroll
// through the size of the label
scrollPane = new JScrollPane(bottomPanel);
scrollPane.setBounds(40, 40, 100, 100);
// set it label here as well.
scrollPane.getViewport().add(bottomPanel);
// I was hoping this would force the scrollbar in but it does nothing
scrollPane
.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane
.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setBounds(50, 30, 300, 50);
JPanel contentPane = new JPanel(null);
contentPane.setPreferredSize(new Dimension(500, 400));
contentPane.add(scrollPane);
topPanel.add(scrollPane, BorderLayout.CENTER);
init();
}
public void init() {
// this sets the grid to empty
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
grid[x][y] = "";
}
}
}
#Override
public void paint(Graphics g) {
// this paints the grid
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.black);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
g2d.drawRect(x * size + startX, y * size + startY, size, size);
if (grid[x][y].equals("")) {
g2d.drawImage(currentBackIcon.getImage(),
x * size + startX, y * size + startY, null);
}
g2d.setColor(Color.black);
g2d.drawRect((x * size) + 1 + startX, (y * size) + 1 + startY,
size, size);
}
}
}
public void drawTile() {
// this isn't enabled which is why you can't paint the grid, however it
// would change the tile of the square you're mouse is on, to the
// current tile, it works and isn't really important for what i need
// help with
PointerInfo a = MouseInfo.getPointerInfo();
Point b = a.getLocation();
int mouseX = (int) b.getX();
int mouseY = (int) b.getY();
int gMX = ((mouseX - 48) / 16) - 4;
int gMY = ((mouseY - 48) / 16) - 3;
grid[gMX][gMY] = currentImage;
repaint();
}
}
scrollPane.getViewport().add(bottomPanel); should be more like scrollPane.getViewportView(bottomPanel);
You shouldn't be painting directly to the frame, as child components can be painted without the notification to the parents, meaning that what ever you've painted could be partially wiped out. Instead, this kind of painting should be done within a custom component which acts as the JScrollPane's, JViewport's view.
A JScrollPane needs two things, first, the size that the component would like to be (the preferredSize) and the size of the viewport view. If the component doesn't implement the Scrollable interface, then the component's preferredSize is used to determine that as well. This is why a JLabel will work.
A JScrollPane has a JViewport as it's primary child component. The JViewport should only have a single component, typically assigned either via JScrollPane#setViewportView or JViewport#setView methods
See How to Use Scroll Panes for more details
Create a custom component that extends JPanel and override it's getPreferredSize method to return the size of the component you want. Override it's paintComponent method and perform you custom painting their.
Overlaying custom painting ontop of other components is more difficult
You can also add JScrollPane in your panel like this
JPanel p = new JPanel();
add(new JScrollPane(p));
I have a JPanel 7width 9 height board. I can also place my pieces on top of the board. My problem now is how I will call the pieces:
pseudo code:
if(whero1 is on row 0 column 5 then....
code is below:
public class Board extends JPanel{
private static final String imageFolderPath = "src/resources/images/";
Dimension dimension = new Dimension(500, 500);
JPanel board;
JLabel piece;
MovePiece mp = new MovePiece(this);
public Board(){
//set size of panel;
this.setPreferredSize(dimension);
this.addMouseListener(mp);
this.addMouseMotionListener(mp);
//create the Board
board = new JPanel();
board.setLayout(new GridLayout(9,7));
board.setPreferredSize(dimension);
board.setBounds(0, 0, dimension.width, dimension.height);
this.add(board);
for (int i = 0; i < 63; i++) {
JPanel square = new JPanel(new BorderLayout());
square.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
board.add(square);
square.setBackground(new Color(185, 156, 107));
}
JLabel whero1 = new JLabel( new ImageIcon(imageFolderPath+"/pieces/bhero.png") );
JPanel panel = (JPanel)board.getComponent(60);
panel.add(whero1);
//I'm trying this, but i.m going nowhere
int x =whero1.getParent().getX();
int y = whero1.getParent().getY();
System.out.println(x);
System.out.println(y);
/*if(x==0&&y==0){
whero1.setIcon(new ImageIcon(imageFolderPath+"/pieces/bdg.png"));
}*/
}
}
The easiest solution would be to maintain some kind of virtual model of the board. In this way you could simply update the state of the game and request that the UI update itself to reflect the state of the model.
Much simpler then trying to interrogate n-depth contains and convert to/from coordinate systems
nb: This...
int x =whero1.getParent().getX();
int y = whero1.getParent().getY();
Is going to return the pixel x/y position of the whereo1s parent's in relation to it's parent container, not convinced that this would really help at all
I'm building a Tic Tac Toe game in Java with a Swing GUI, and it renders correctly in Ubuntu 10.4 and Windows XP. This is how it looks like in Ubuntu:
When I copied the bin-folder with all the class files and tried to run the program in Windows 7 it looked like this instead:
I just can't understand what's wrong. As I said, it works perfectly in Ubuntu 10.4 and Windows XP.
I would be very happy if someone could help me out! I'll post the code related to the GUI, just in case it is needed to solve the problem.
Here is the code I use to initialize the GUI:
//Initializing GUI.
frame = new JFrame(); //Creating the window.
frame.setTitle("Tic Tac Toe"); //Setting the title of the window.
frame.addMouseListener(this);
frame.getContentPane().add(BorderLayout.CENTER, grid.getPanel()); //Adding the grid panel.
info = new JLabel(" Initializing game..."); //Creating info text.
frame.getContentPane().add(BorderLayout.SOUTH, info); //Adding info text.
//Setting GUI properties.
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.setVisible(true);
The panel with the grid itself is created in my GameGrid class, which have a method "JPanel getPanel()". Here is the initialization of that panel (the code belongs in the constructor of GameGrid):
GridBox temp;
layout = new GridLayout(getHeight(), getWidth());
panel = new JPanel(layout);
panel.setBorder(
BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder("Click in a box to place a marker:"),
BorderFactory.createEmptyBorder(5,5,5,5)));
//Creating a GridBox for each cell, and adding them to the panel in the right order..
for(int i = 0; i < getHeight(); i++) {
for(int j = 0; j < getWidth(); j++) {
temp = new GridBox(j, i);
temp.addMouseListener(listener);
panel.add(temp);
}
}
GridBox is a subclass of JPanel, which I modified to automatically show the contents of the grid at the coordinates specified.
class GridBox extends JPanel {
private static final long serialVersionUID = 1L;
int fontsize, x, y, value, signHeight, signWidth;
char print;
FontMetrics fm;
LineMetrics lm;
public GridBox(int a, int b) {
x = a; //TODO - input control
y = b;
}
public Move getMove() {
Move m = new Move(x, y);
return m;
}
public void paintComponent(Graphics g) {
Border blackline = BorderFactory.createLineBorder(Color.black);
setBorder(blackline);
Dimension size = getSize();
Rectangle2D rect;
fontsize = (int)(size.getHeight()*0.75);
value = getGridValue(x, y);
if(value == EMPTY)
print = ' ';
else if(value == 0)
print = 'X';
else if(value == 1)
print = 'O';
else
print = (char)value;
Font font = new Font("Times New Roman", Font.PLAIN, fontsize);
g.setFont(font);
fm = g.getFontMetrics();
rect = fm.getStringBounds(Character.toString(print), g);
signHeight = (int)rect.getHeight();
signWidth = (int)rect.getWidth();
g.setColor(Color.black);
g.drawString(Character.toString(print), (size.width/2)-(signWidth/2), (size.height/2)-(signHeight/2)+fm.getAscent());
}
}
Thanks in advance!
There's an obvious problem in the you change the border whilst repainting the component. That's going to cause all sorts of problems.
Also, I don't see where you paint the background of the panel. You should have
super.paintComponent(g);
at the top of the method.