How can I center a text vertically within a JTextArea? - java

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);
}
}

Related

JButtons incorrect size

I want to draw a 13x13 tiles board using JFrame.
Here's the code:
public static void drawBoard() {
final int TILE_SIZE = 60;
final int TILES = 13;
JFrame jFrame = new JFrame("Board");
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setSize(TILE_SIZE * TILES + 17, TILE_SIZE * TILES);
jFrame.setResizable(false);
JButton button = null;
ImageIcon icon = null;
for (int y = 0; y < TILES; y++) {
for (int x = 0; x < TILES; x++) {
button = new JButton(alphabet[x] + "" + alphabet[y]);
button.setName(alphabet[x] + "" + alphabet[y]);
button.setBounds(TILE_SIZE * y, TILE_SIZE * x, TILE_SIZE, TILE_SIZE);
button.addActionListener(new Clicked());
button.setBackground(Color.WHITE);
jFrame.getContentPane().add(button);
}
}
jFrame.setVisible(true);
}//end drawBoard
Now, when I run the code, it shows me a grid of buttons, but the one at the bottom right corner is the same size of the frame.
but the one at the bottom right corner is the same size of the frame.
Swing was designed to be used with layout managers. The layout manager will determine the size/location of a component based on the rules of the layout manager.
In the case of a JFrame, the default layout manager is the BorderLayout. When you add a component to the BorderLayout without using a "constraint" the component is added to the CENTER. However only one component can be added to the CENTER. So only the last component added is give a size/location by the layout manager. In this case the rule is to make the button the size of the space available in the frame.
If you want to have a grid, then you should be using a GridLayout. Then the layout manager will make each button the same size.
Read the section from the Swing tutorial on Layout Managers for more information. There are working examples for both the BorderLayout and the GridLayout.
You should really look into a layout that handles the component size instead of setting it on the component itself (See: MIG Layout), but this should get you were you want to be.
public static void drawBoard(){
final int TILE_SIZE = 60;
final int TILES = 13;
JFrame jFrame = new JFrame("Board");
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setSize(TILE_SIZE * TILES + 17, TILE_SIZE * TILES);
jFrame.getContentPane().setLayout(new GridLayout(TILES, TILES));
jFrame.setResizable(false);
JButton button = null;
Dimension dim = new Dimension(TILE_SIZE, TILE_SIZE);
for (int y = 0; y < TILES; y++) {
for (int x = 0; x < TILES; x++) {
button = new JButton(alphabet[x] + "" + alphabet[y]);
button.setSize(dim);
button.addActionListener(new Clicked());
button.setBackground(Color.WHITE);
jFrame.getContentPane().add(button);
}
}
jFrame.setVisible(true);
}

Scrolling through a JPanel within a JScrollingPanel

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));

How should I drag a JLabel from one JPanel in a JFrame onto a JTextField in another JPanel in the same JFrame?

In short, I want to set the text of a JLabel to be that of a JTextField in a JPanel (pnlUser) and then drag the JLabel across the screen from JPanel onto another JTextField in another JPanel (pnlGrid).
Here are the details.
I have written a "Solitaire Scrabble" program. The user can either position the text cursor in a grid cell (a JTextField in pnlGrid) and type a letter that is in the list of "User letters" (a JTextField in pnlUser) OR the user can simulate dragging a letter from "User letters" and dropping it into the destination grid cell in pnlGrid.
I say "simulate" because the selected letter is not actually dragged across the screen. I use the mouse pointer HAND_CURSOR to make the drag/drop as real as possible, but I haven't figured out how to make the HAND_CURSOR "grab" the letter and physically drag the letter across the board to its destination.
As it is, the letter is highlighted but left in the "User letters" area while the HAND_CURSOR moves along the grid during the drag operation. When it gets to the destination cell in pnlGrid and the mouse button is released, the letter is erased from "User letters" and suddenly appears in the grid cell.
So the letter is more or less "teleported" (beam me up, Scotty) from "User letters" to a grid cell. This is too abstract. I want the user letter to be at the tip of the HAND_CURSOR's pointing finger and be dragged along the grid into the grid cell where it will be dropped, as shown in the 3 pictures below.
I've successfully made it happen in a small test program (source below) using JLayeredPane, but I can't make it happen in the game. But I knew nothing about JLayeredPane until two days ago so I don't really know what I'm doing. (I adapted an Oracle tutorial program that demos JLayeredPane.)
I just read about the "glass pane" and thought it would maybe be easier to implement until I downloaded the source for that demo, which is quite long, so since it's totally new and will be even harder to adapt.
So I thought before I spend more hours in frustration I should ask:
Is a JLayeredPane or a setGlassPane approach appropriate? Is there an easier or better way to drag a JLabel from one JPanel onto another another JPanel?
(The approach in the program is to determine which "User letter" is being pointed at, store that letter in a JLabel, and then make sure that during mouseDragged the HAND_CURSOR fingertip is right at the bottom center of the letter.)
package mousemoveletter;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import static java.awt.Color.*;
import java.awt.event.*;
import static javax.swing.SwingUtilities.invokeLater;
public class LayeredPaneDemo extends JPanel
{
private static final int USER7 = 7;
static Cursor HAND = new Cursor(Cursor.HAND_CURSOR);
static Cursor ARROW = new Cursor(Cursor.DEFAULT_CURSOR);
private static JLayeredPane layeredPane;
private static JLabel lblToMove;
private static JPanel pnlUser;
private static JPanel pnlGrid;
private static final JTextField[] txtUser = new JTextField[USER7];
public LayeredPaneDemo() // constructor
{
pnlGrid = new JPanel();
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
layeredPane = new JLayeredPane();
layeredPane.setPreferredSize(new Dimension(240, 240));
pnlGrid.setSize(140, 140);
pnlGrid.setBorder(new EtchedBorder(RED, GREEN));
pnlGrid.setBackground(YELLOW);
lblToMove = new JLabel("XXX");
lblToMove.setSize(new Dimension(40,40));
layeredPane.add(pnlGrid, 0,0);
layeredPane.add(lblToMove, new Integer(0), -1);
add(layeredPane);
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("LayeredPaneDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JComponent newContentPane = new LayeredPaneDemo();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
makeUser();
frame.add(pnlUser);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
invokeLater(new Runnable()
{
public void run() {
createAndShowGUI();
}
});
}
private static void makeUser(){
pnlUser = new JPanel(new GridLayout(1,USER7));
pnlUser.setPreferredSize(new Dimension(225, 50));
pnlUser.setBackground(Color.green);
pnlUser.setBorder(BorderFactory.createLineBorder(Color.BLUE));
for(int k = 0; k < USER7; k++)
{
txtUser[k] = new JTextField("" + (char)(Math.random()*26+65));
txtUser[k].setName("" + k);
txtUser[k].setEditable(false);
txtUser[k].addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseDragged(MouseEvent e)
{
lblToMove.setCursor(HAND);
int w = Integer.parseInt(e.getComponent().getName());
lblToMove.setText(txtUser[w].getText());
layeredPane.setLayer(lblToMove, 0, 0);
lblToMove.setLocation(e.getX() + (e.getComponent().getWidth())*w,
e.getY() + layeredPane.getHeight() - e.getComponent().getHeight()/2);
};
});
txtUser[k].addMouseListener(new MouseAdapter()
{
public void mouseReleased(MouseEvent e)
{
lblToMove.setCursor(ARROW);
}
});
pnlUser.add(txtUser[k]);
}
}
}
Thanks to #trashgod, I figured it out by following his links to this example and variation; I adapted the drag/drop of the chessboard found there to my own particular needs for "Scrabble".
The code below is not final code for my Solitaire Scrabble program, but proof-of-concept, possibly usable by others wishing to drag a cell from a 1xN grid onto a MxM grid.
package components;
import java.awt.*;
import static java.awt.BorderLayout.NORTH;
import static java.awt.BorderLayout.SOUTH;
import java.awt.event.*;
import static java.lang.Integer.parseInt;
import javax.swing.*;
import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE;
import javax.swing.event.MouseInputAdapter;
public class ChessBoard //implements MouseListener, MouseMotionListener
{
static Point parentLocation;
int homeRow, homeCol; // where to restore moved user letter if dropped on occupied cell
static int N = 11; // NxN 'chessboard' squares
static int S = 44; // square dimensions: SxS
static int W ; // chessboard dimensions: WxW
static int USER7 = 7;
static Font dragFont;
static JFrame frame;
JLayeredPane layeredPane;
static JPanel gamePanel, // encompasses both pnlGrid and pnlUser
pnlGrid,
pnlUser;
JLabel userDragLetter = new JLabel(); // main item to drag around or restore if needed
int xAdjustment, yAdjustment; // how to locate drops accurately
String userLetters[] ;
public ChessBoard() // constructor
{
W = S*N;
dragFont = new Font("Courier", Font.PLAIN, S);
userLetters = new String[USER7];
for (int i = 0; i < USER7; i++)
userLetters[i] = "" + (char)(65 + Math.random()*26);
Dimension gridSize = new Dimension(W, W);
Dimension userSize = new Dimension(W, S);
Dimension gameSize = new Dimension(W, (W + S));
frame = new JFrame();
frame.setSize(new Dimension(gameSize)); // DO NOT USE PREFERRED
layeredPane = new JLayeredPane();
layeredPane.setPreferredSize( gameSize ); // NO PREFERRED => NO GRID!
gamePanel = new JPanel();
// **EDIT** LOSE THIS LINE gamePanel.setLayout(new BorderLayout());
gamePanel.setPreferredSize(gameSize);
pnlGrid = new JPanel();
pnlGrid.setLayout(new GridLayout(N, N));
pnlGrid.setPreferredSize( gridSize );
pnlGrid.setBounds(0, 0, gridSize.width, gridSize.height);
pnlUser = new JPanel();
pnlUser.setLayout(new GridLayout(1, N));
pnlUser.setPreferredSize(userSize);
pnlUser.setBounds(0, gridSize.height, userSize.width, userSize.height);
layeredPane.add(pnlGrid, JLayeredPane.DEFAULT_LAYER); // panels to drag over
layeredPane.add(pnlUser, JLayeredPane.DEFAULT_LAYER); // " "
for (int i = 0; i < N; i++){
for (int j = 0; j < N; j++){
JPanel square = new JPanel();
square.setBackground( (i + j) % 2 == 0 ? Color.red : Color.white );
pnlGrid.add( square );
}
}
for (int i = 0; i < N; i++) {
JPanel square = new JPanel(new BorderLayout());
square.setBackground(Color.YELLOW);
pnlUser.add(square);
}
for (int i = 0; i < USER7; i++)
addPiece(i, 0, userLetters[i]);
gamePanel.addMouseListener(new MouseInputAdapter()
{
public void mousePressed (MouseEvent e){mousePressedActionPerformed (e);}
public void mouseReleased(MouseEvent e){mouseReleasedActionPerformed(e);}
});
gamePanel.addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseDragged(MouseEvent me){mouseDraggedActionPerformed(me);}
});
// **EDIT: LOSE THE NEXT TWO LINES AND REPLACE BY THE LINE AFTER THEM**
// gamePanel.add(layeredPane, NORTH);
// gamePanel.add(pnlUser, SOUTH);
gamePanel.add(layeredPane);
}
private void addPiece(int col, int row, String glyph) {
JLabel piece = new JLabel(glyph, JLabel.CENTER);
piece.setFont(dragFont);
JPanel panel = (JPanel) pnlUser.getComponent(col + row * N);
piece.setName("piece " + glyph + " # " + row + " " + col);
panel.add(piece);
}
void mousePressedActionPerformed(MouseEvent e)
{
userDragLetter = null; // signal that we're not dragging if no piece is in the square
gamePanel.setCursor(new Cursor(Cursor.HAND_CURSOR));
Component c = pnlGrid.findComponentAt(e.getX(), e.getY());
if(c != null)
return; // Illegal to click pnlGrid
c = pnlUser.findComponentAt(e.getX(), e.getY() - pnlGrid.getHeight());
if(c == null | c instanceof JPanel)
return; // letter already played; can't drag empty cell
parentLocation = c.getParent().getLocation();
xAdjustment = parentLocation.x - e.getX();
yAdjustment = parentLocation.y - e.getY() + gamePanel.getHeight() - pnlUser.getHeight();
userDragLetter = (JLabel)c;
userDragLetter.setPreferredSize(new Dimension(S, S)); // prevent 2 letters in a square
userDragLetter.setLocation(e.getX() + xAdjustment, e.getY() + yAdjustment);
layeredPane.add(userDragLetter, JLayeredPane.DRAG_LAYER);
homeRow = parseInt(userDragLetter.getName().substring(10,11)); // save restore location
homeCol = parseInt(userDragLetter.getName().substring(12,13));
}
void mouseDraggedActionPerformed(MouseEvent me)
{
if (userDragLetter == null)
return; // nothing to drag
int x = me.getX() + xAdjustment; // make sure grid cell will be chosen in-bounds
int xMax = layeredPane.getWidth() - userDragLetter.getWidth();
x = Math.min(x, xMax);
x = Math.max(x, 0);
int y = me.getY() + yAdjustment;
int yMax = layeredPane.getHeight() - userDragLetter.getHeight();
y = Math.min(y, yMax);
y = Math.max(y, 0);
if(y >= pnlGrid.getHeight())
return; // can't drag to location off grid
userDragLetter.setLocation(x, y);
}
void mouseReleasedActionPerformed(MouseEvent e)
{
//**EDIT: CHANGED NEXT LINE**
gamePanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
if (userDragLetter == null)
return; // nothing to drag; nothing to release
// Make sure the chess piece is no longer painted on the layered pane
userDragLetter.setVisible(false);
layeredPane.remove(userDragLetter);
userDragLetter.setVisible(true);
int xMax = layeredPane.getWidth() - userDragLetter.getWidth();
int x = Math.min(e.getX(), xMax);
x = Math.max(x, 0);
int yMax = layeredPane.getHeight()- userDragLetter.getHeight();
int y = Math.min(e.getY(), yMax);
y = Math.max(y, 0);
Component c = pnlGrid.findComponentAt(x, y); // find deepest nested child component
if(c == null) // then grid cell is unoccupied so ...
c = pnlUser.findComponentAt(x, y); // see if there's a letter there ...
if(c == null | (c instanceof JLabel)){ // and if illegal or there is one, put it back...
userDragLetter.setLocation(parentLocation.x + xAdjustment,
parentLocation.y + yAdjustment + gamePanel.getHeight());
userDragLetter.setVisible(true);
addPiece(homeCol, homeRow,userDragLetter.getName().substring(6,7));
layeredPane.remove(userDragLetter);
return;
}
else // but if NO letter ...
{
Container parent = (Container)c;
parent.add( userDragLetter ); // put one in the grid cell
parent.validate();
}
userDragLetter.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
public static void main(String[] args)
{
new ChessBoard();
frame.add(gamePanel);
frame.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
// frame.setResizable( false );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}

JTable inside a JScrollPane with a fixed width

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!

Java - Swing GUI renders incorrectly in Windows 7

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.

Categories