JTable Scrolling to a Specified Row Index - java

I have a JTable that is within a JScrollPane. Rows are added to the table at runtime based on events that happen in my application. I want to have the scoll pane scroll to the bottom of the table when a new row is added to the table.
For JLists There is the [ensureIndexIsVisible][1]() that forces a particular index in the list to be visible. I'm looking for the same thing but for a JTable. It looks like I might have to manually move the scrolling view on the scroll pane but I figured there had to be an easier way.

It's very easy, JTable has scrollRectToVisible method too. If you want, you can try something like this to make scrollpane go to to the bottom if a new record is added :
jTable1.getSelectionModel().setSelectionInterval(i, i);
jTable1.scrollRectToVisible(new Rectangle(jTable1.getCellRect(i, 0, true)));
Where i is last added record.

See this example : http://www.exampledepot.com/egs/javax.swing.table/Vis.html
update: the link is now obsolete, here is the code (from http://smi-protege.stanford.edu/repos/protege/protege-core/trunk/src/edu/stanford/smi/protege/util/ComponentUtilities.java )
public static void scrollToVisible(JTable table, int rowIndex, int vColIndex) {
if (!(table.getParent() instanceof JViewport)) {
return;
}
JViewport viewport = (JViewport)table.getParent();
// This rectangle is relative to the table where the
// northwest corner of cell (0,0) is always (0,0).
Rectangle rect = table.getCellRect(rowIndex, vColIndex, true);
// The location of the viewport relative to the table
Point pt = viewport.getViewPosition();
// Translate the cell location so that it is relative
// to the view, assuming the northwest corner of the
// view is (0,0)
rect.setLocation(rect.x-pt.x, rect.y-pt.y);
table.scrollRectToVisible(rect);
// Scroll the area into view
//viewport.scrollRectToVisible(rect);
}

JList internally use scrollRectToVisible and specify the coordinates to scroll to. I think you will have to recode a similar functionality for JTable.

The first answer works well, but the selected row gets positioned at the bottom of the table. So I created this modified version:
private void scrollToVisible(int rowIndex, int vColIndex ) {
JTable table = getTablePanel().getTable();
if (!(table.getParent() instanceof JViewport)) {
return;
}
if (table.getRowCount()<1){
return;
}
JViewport viewport = (JViewport)table.getParent();
// view dimension
Dimension dim = viewport.getExtentSize();
// cell dimension
Dimension dimOne = new Dimension(0,0);
// This rectangle is relative to the table where the
// northwest corner of cell (0,0) is always (0,0).
Rectangle rect = table.getCellRect(rowIndex, vColIndex, true);
Rectangle rectOne;
if (rowIndex+1<table.getRowCount()) {
if (vColIndex+1<table.getColumnCount())
vColIndex++;
rectOne = table.getCellRect(rowIndex+1, vColIndex, true);
dimOne.width=rectOne.x-rect.x;
dimOne.height=rectOne.y-rect.y;
}
// '+ veiw dimension - cell dimension' to set first selected row on the top
rect.setLocation(rect.x+dim.width-dimOne.width, rect.y+dim.height-dimOne.height);
table.scrollRectToVisible(rect);
}
Now the selected row gets positioned at the top of the table.

It seems to me a lot easier to set the viewport position instead of scrolling the table. Following is my code.
public void scrollCellToView(int rowIndex, int vColIndex) {
if (!(this.getParent() instanceof JViewport)) {
return;
}
JViewport viewport = (JViewport) this.getParent();
Rectangle rect = this.getCellRect(rowIndex, vColIndex, true);
Rectangle viewRect = viewport.getViewRect();
int x = viewRect.x;
int y = viewRect.y;
if (rect.x >= viewRect.x && rect.x <= (viewRect.x + viewRect.width - rect.width)){
} else if (rect.x < viewRect.x){
x = rect.x;
} else if (rect.x > (viewRect.x + viewRect.width - rect.width)) {
x = rect.x - viewRect.width + rect.width;
}
if (rect.y >= viewRect.y && rect.y <= (viewRect.y + viewRect.height - rect.height)){
} else if (rect.y < viewRect.y){
y = rect.y;
} else if (rect.y > (viewRect.y + viewRect.height - rect.height)){
y = rect.y - viewRect.height + rect.height;
}
viewport.setViewPosition(new Point(x,y));
}

Related

trying to drag something using a JLayeredPane

I'm trying to put a drag-n-drop operation into a program of mine; I found the following example that illustrates a lot of what I'm trying to do:
package sandbox;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
/**
* Example showing the use of a JLayeredPane to implement dragging an object
* across a JPanel containing other objects.
* <P>
* Basic idea: Create a JLayeredPane as a container, then put the JPanel containing
* the application's components or whatever in the JLayeredPane.DEFAULT_LAYER layer of that layered pane.
* The code is going to drag a JComponent object by calling JComponent.setPosition(x,y)
* on the component. When a mouse is clicked on the panel to start the dragging, put the
* component on the drag layer of the layered pane; as it is dragged, continue to call
* setPosition to move it. When the mouse is released, use the x.y position of the release
* to decide what to do with it next.
*
*/
public class ChessBoard extends JFrame implements MouseListener, MouseMotionListener
{
private static final long serialVersionUID = 1L;
JLayeredPane layeredPane;
JPanel chessBoard;
JLabel chessPiece;
int xAdjustment;
int yAdjustment;
public ChessBoard()
{
Dimension boardSize = new Dimension(600, 600);
// Use a Layered Pane for this application
layeredPane = new JLayeredPane();
layeredPane.setPreferredSize( boardSize );
layeredPane.addMouseListener( this );
layeredPane.addMouseMotionListener( this );
getContentPane().add(layeredPane);
//debug
LayoutManager lm = layeredPane.getLayout();
System.out.println("Layered pane layout name is " + (lm == null? "<null>" : lm.getClass().getName()));
// Add a chess board to the Layered Pane on the DEFAULT layer
chessBoard = new JPanel();
chessBoard.setLayout( new GridLayout(8, 8) );
chessBoard.setPreferredSize( boardSize );
chessBoard.setBounds(0, 0, boardSize.width, boardSize.height);
layeredPane.add(chessBoard, JLayeredPane.DEFAULT_LAYER);
// Build the Chess Board squares
// We use an 8x8 grid, and put a JPanel with BorderLayout on each square.
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
JPanel square = new JPanel( new BorderLayout() );
square.setBackground( (i + j) % 2 == 0 ? Color.gray : Color.white );
chessBoard.add( square );
}
}
// Add a few pieces to the board
// we do this with an ImageIcon that gets added to the square's panel.
ImageIcon duke = new ImageIcon("granary.gif"); // this is the image to add to each space.
addDuke(duke, 0);
addDuke(duke, 6);
addDuke(duke, 15);
addDuke(duke, 20);
}
private void addDuke(ImageIcon duke, int boardPosition)
{
JLabel pieceLabel = new JLabel(duke);
JPanel piecePanel = (JPanel)chessBoard.getComponent(boardPosition);
piecePanel.add(pieceLabel);
}
/*
** Add the selected chess piece to the dragging layer so it can be moved
*/
public void mousePressed(MouseEvent e)
{
// get the component where the user pressed; iff that's not a panel,
// we'll put it on the dragging layer.
chessPiece = null; // change1 swap the change1 lines
// chessPiece = new JLabel(new ImageIcon("house1x1.gif")); // change1
Component c = chessBoard.findComponentAt(e.getX(), e.getY());
if (c instanceof JPanel) return;
// get the location of the panel containing the image panel, i.e.,
// the square's panel. we adjust the location to which we move the
// piece by this amount so the piece doesn't 'snap to' the cursor
// location.
Point parentLocation = c.getParent().getLocation();
xAdjustment = parentLocation.x - e.getX();
yAdjustment = parentLocation.y - e.getY();
chessPiece = (JLabel)c; // change2 - comment out
chessPiece.setLocation(e.getX() + xAdjustment, e.getY() + yAdjustment);
layeredPane.add(chessPiece, JLayeredPane.DRAG_LAYER); // evidently this removes it from the default layer also.
layeredPane.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
}
/*
** Move the chess piece around
*/
public void mouseDragged(MouseEvent me)
{
if (chessPiece == null) return;
// The drag location should be within the bounds of the chess board
int x = me.getX() + xAdjustment;
int xMax = layeredPane.getWidth() - chessPiece.getWidth();
x = Math.min(x, xMax);
x = Math.max(x, 0);
int y = me.getY() + yAdjustment;
int yMax = layeredPane.getHeight() - chessPiece.getHeight();
y = Math.min(y, yMax);
y = Math.max(y, 0);
chessPiece.setLocation(x, y); // evidently this works for whatever layer contains the piece.
// also, the layout manager of its new home is evidently not the same as lower layers.
}
/*
** Drop the chess piece back onto the chess board
*/
public void mouseReleased(MouseEvent e)
{
layeredPane.setCursor(null);
if (chessPiece == null) return;
// Make sure the chess piece is no longer painted on the layered pane
chessPiece.setVisible(false);
layeredPane.remove(chessPiece);
chessPiece.setVisible(true);
// The drop location should be within the bounds of the chess board
int xMax = layeredPane.getWidth() - chessPiece.getWidth();
int x = Math.min(e.getX(), xMax);
x = Math.max(x, 0);
int yMax = layeredPane.getHeight() - chessPiece.getHeight();
int y = Math.min(e.getY(), yMax);
y = Math.max(y, 0);
Component c = chessBoard.findComponentAt(x, y);
Container parent = null;
if (c instanceof JLabel)
{
parent = c.getParent(); // there's a piece on the square already; remove it from the panel.
parent.remove(0);
}
else
{
parent = (Container)c;
}
parent.add( chessPiece ); // this adds the piece back to the default layer
parent.validate();
}
public void mouseClicked(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public static void main(String[] args)
{
JFrame frame = new ChessBoard();
frame.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
frame.setResizable( false );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
This does the job for the case of a chessboard, namely allowing a user to drag any of the pieces on the chessboard to a different square.
In the application I'm writing, the item being dragged doesn't exist until the user clicks something that starts the dragging operation. I'm having trouble figuring out how to do that creation and have it show up.
My current attempt is on lines labelled 'change1' and 'change2'; you swap the two lines with 'change1', and comment out the one with 'change2'. In other words, create the JLabel when the mouse is pressed, and (hopefully) drag that around. But when I run that, the image does not show up on press or during dragging, but DOES show up on the square at the end of the drag.
What have I missed here? I am a little confused by JLayeredPane, the javadoc says that it will follow layout rules, but not whether the layout rules apply to all the components on all layers, or just the bottom, or to all layers but separately, what? I wouldn't think it was a layout issue, but I don't know what's wrong. Do I need some kind of UI update somewhere? I thought adding the component would invalidate the panel.
The original code was written assuming you were clicking on a chess piece.
Now you want to click on an empty cell which will require the following changes.
The chess board consists of JPanels in each cell. Some cells will contain a JLabel represent a chess piece. The current logic in the mousePressed event is expecting you to click on a JLabel otherwise some processing is skipped.
You need to remove:
//if (c instanceof JPanel) return;
By default a Swing component has a 0 size when it is created.
You need to give it a size:
chessPiece.setSize( chessPiece.getPreferredSize() );
The positioning logic for the label is based on finding the location of the clicked component relative to the parent. Since there is no label, this logic is now based on the panel relative to the layered pane.
You need to adjust this logic to make it relative to parent panel again:
//Point parentLocation = c.getParent().getLocation();
Point parentLocation = c.getLocation();
My updated mousePressed method looks like:
public void mousePressed(MouseEvent e)
{
// get the component where the user pressed; iff that's not a panel,
// we'll put it on the dragging layer.
//chessPiece = null; // change1 swap the change1 lines
chessPiece = new JLabel(new ImageIcon("dukewavered.gif")); // change1
chessPiece.setSize( chessPiece.getPreferredSize() );
Component c = chessBoard.findComponentAt(e.getX(), e.getY());
//if (c instanceof JPanel) return;
// get the location of the panel containing the image panel, i.e.,
// the square's panel. we adjust the location to which we move the
// piece by this amount so the piece doesn't 'snap to' the cursor
// location.
//Point parentLocation = c.getParent().getLocation();
Point parentLocation = c.getLocation();
xAdjustment = parentLocation.x - e.getX();
yAdjustment = parentLocation.y - e.getY();
//chessPiece = (JLabel)c; // change2 - comment out
chessPiece.setLocation(e.getX() + xAdjustment, e.getY() + yAdjustment);
layeredPane.add(chessPiece, JLayeredPane.DRAG_LAYER); // evidently this removes it from the default layer also.
layeredPane.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
}
Note the above changes will break the old functionality of being able to drag an existing label. If you need functionality for both then your logic will be determined on whether you click on a JLabel (in which case you use the old logic) or click on a JPanel (in which case you use the newer logic).

How to keep dragged Node in front of others (JavaFX 8)?

I'm trying to implement a chess board with draggable pieces as seen below. However, I'm unable to keep the piece being dragged to stay in front of other nodes to below or right to it. Left and up seems to work fine.
I tried to solve this by declaring the StackPanes forming the checkered background first and all the pieces only after that, as I read Java assigns the z-index based on the order in which the Nodes are added to their Parents. This approach is reflected below. I also tried creating a Group and adding both StackPanes and ImageViews to it in order to be able to use toFront(). Resulted in only the coordinate labels being shown.
How can I achieve the functionality I'm after?
This method creates the board:
public Parent chessBoard() {
GridPane board = new GridPane();
StackPane[][] cells = new StackPane[8][8];
// Create the board first
// (For dragging pieces to work correctly, draggable pieces must be
// added after the whole board, since z-index cannot be set explicitly
// in JavaFX.
for (int row = 0; row < 10; row++) {
for (int col = 0; col < 10; col++) {
// x and y in chess coordinate system (0-indexed)
int[] invertedY = {-1,7,6,5,4,3,2,1,0,-1};
int x = col - 1;
int y = invertedY[row];
// Coordinate labels
String[] abcLabels = {"A","B","C","D","E","F","G","H"};
if (row == 9 || row == 0) {
if (col == 0 || col == 9) continue;
Label label = new Label(abcLabels[x]);
label.setTextAlignment(TextAlignment.CENTER);
board.add(label, col, row);
continue;
} else if (col == 0 || col == 9) {
Label label = new Label(Integer.toString(y + 1));
board.add(label, col, row);
continue;
}
// Cell background color
Square square = game.getBoard().getSquare(x, y);
Color color = square.getColor() == ChessColor.BLACK
? Color.PERU : Color.BLANCHEDALMOND;
StackPane cell = cells[y][x] = new StackPane();
cell.setMaxSize(60, 60);
cell.setMinSize(60, 60);
cell.setBackground(new Background(
new BackgroundFill(color, null, null)));
board.add(cell, col, row);
}
}
// Finally, add pieces to their respective cells
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
Square square = game.getBoard().getSquare(x, y);
Piece occupant = square.getOccupant();
if (occupant != null) {
String path = "/resources/" + occupant + ".png";
Image image =
new Image(getClass().getResourceAsStream(path));
DraggablePieceIcon imageView =
new DraggablePieceIcon(image);
imageView.setManaged(false);
cells[y][x].getChildren().add(imageView);
}
}
}
return board;
}
This class makes the draggable icons:
public class DraggablePieceIcon extends ImageView {
private double mouseX;
private double mouseY;
public DraggablePieceIcon(Image image) {
super(image);
setOnMousePressed(event -> {
mouseX = event.getSceneX();
mouseY = event.getSceneY();
});
setOnMouseDragged(event -> {
double deltaX = event.getSceneX() - mouseX;
double deltaY = event.getSceneY() - mouseY;
relocate(getLayoutX() + deltaX, getLayoutY() + deltaY);
mouseX = event.getSceneX();
mouseY = event.getSceneY();
});
}
}
And here's what I'm seeing:
You're adding the cells row by row from left to right. Since you add the pieces to the cells, the descendants of a cell cover the contents of cells in rows above or in the same row and left to the cell and will be covered by contents of all other cells.
To fix this you could make the parent of a dragged item the topmost node in the GridPane:
public DraggablePieceIcon(Image image) {
super(image);
setOnMousePressed(event -> {
mouseX = event.getSceneX();
mouseY = event.getSceneY();
// make cell containing this piece the top-most cell
this.getParent().toFront()
});
...
}
Note that this solution will require you to implement some logic that makes the pieces children of the cells they are moved to and move the pieces to the center of those cells. Otherwise a piece could be covered by other cells, if you drag the piece inside such a cell later...
An alternative would be to make the pieces children of the GridPane itself. You allow the pieces to be dragged around independent of the cells anyways; the association between cell and piece is important for the model (i.e. in this case the implementation of the chess rules) not for the view and usually these parts are kept seperate.

Weird rendering result when JFrame is set as not resizable, but not when it is resizable

I'm creating a game and I have a JFrame with a custom Canvas in it. The weird thing is that when I set the JFrame to BE RESIZABLE, the rendering result looks like this:
But when I set the JFrame to NOT BE RESIZABLE, the result looks like this:
The method which adds the texture's pixel array to the BufferedImage's pixel array looks like this:
// DRAW A TEXTURE ON THE CANVAS
public void drawTexture(Texture texture, Vector2i location) {
// Store the current texture coordinates
int tx = 0, ty = 0;
// Scroll through each pixel on the screen horizontally (begin at the X coordinate specified by the 'location')
for(int x = location.x; (x < getWidth() && x < location.x + texture.getWidth()); x++) {
// Scroll through each pixel on the screen vertically (begin at the Y coordinate specified by the 'location')
for(int y = location.y; (y < getHeight() && y < location.y + texture.getHeight()); y++) {
// Set each pixel to the color of the corresponding pixel on the Texture
pixels[x + y * getWidth()] = texture.getPixels()[tx + ty * texture.getWidth()];
// Add one to the texture Y coordinate
ty++;
}
// Reset the ty variable
ty = 0;
// Add one to the texture X coordinate
tx++;
}
}
And the BufferedImage is drawn inside of a loop in the run method of my custom canvas.
The custom canvas class (extends Canvas offcourse) implements Runnable, and it also contains an own Thread for rendering.
I wonder if anyone know why this happens, and maybe how to fix it, because I can't figure it out...
Well, I found the working answer here, because I found out that the Canvas was bigger than what I had set it to, so I googled for that, and that answer fixed both problems.
Quote from the answer to the other question:
Swap the pack and setResizable calls, so that pack is the second call.
and
This is a known "issue"
Question here.

Color Grid: figure out which box was clicked

My question concerns the Color Grid teechart. I'm trying to write code so that when the user touches a box on the grid, only that box color will change. I find the using chart.getHeight() and chart.getWidth() give the whole area of the chart and not just the color grid dimensions. So right now, I'm estimating the length and width of the grid in pixels to estimate the box that the user touched. Is there any way that I can figure out the exact amount of pixels of just the color grid length and height? Additionally, I noticed a "clicked" method in the Color Grid api. Is there anything already built-in that would allow me to find which box the user touched/clicked? Thanks!
I've made a simple example that uses the seriesClicked event. However, it seems to give a correct index but the cell colored seems to be in the opposite row:
oldIndex = -1;
tChart1.getAspect().setView3D(false);
tChart1.getLegend().setVisible(false);
ColorGrid col1 = new ColorGrid(tChart1.getChart());
col1.setColorEach(true);
for (int x=0; x<10; x++)
for (int z=0; z<10; z++)
col1.add(x, 1, z, Color.random());
col1.addSeriesMouseListener(new SeriesMouseAdapter() {
#Override
public void seriesClicked(SeriesMouseEvent e) {
ColorGrid myGrid = (ColorGrid)tChart1.getSeries(0);
int valueIndex = myGrid.clicked(e.getPoint().x, e.getPoint().y);
if (valueIndex > -1) {
if (oldIndex > -1) {
myGrid.getColors().setColor(oldIndex, oldColor);
}
oldIndex = valueIndex;
oldColor = myGrid.getValueColor(valueIndex);
myGrid.getColors().setColor(valueIndex, Color.red);
tChart1.getHeader().setText(String.valueOf(valueIndex));
tChart1.getSeries(0).repaint();
}
}
});
I'll add to the defect list the need to revise the clicked function for the ColorGrid (TJ71016603)
In the meanwhile, a workaround could be to modify the given valueIndex as follows:
if (valueIndex > -1) {
valueIndex = valueIndex / myGrid.getNumZValues() * myGrid.getNumZValues() + (myGrid.getNumZValues()-1 - (valueIndex % myGrid.getNumZValues()));
if (oldIndex > -1) {
//...
}

How do I create a bar chart using the Java prefuse library?

I've currently got prefuse to plot a scatter graph, where the X axis is the computer name and the Y axis is its temperature. How do I get it to draw bars showing the values instead of discrete points?
I'm currently using the following code to render the points:
ShapeAction shape = new ShapeAction(group, Constants.SHAPE_RECTANGLE);
ColorAction strokeColor = new DataColorAction(group, dataType, Constants.NUMERICAL, VisualItem.STROKECOLOR, colorPalette);
ActionList draw = new ActionList();
draw.add(shape);
draw.add(strokeColor);
draw.add(new ColorAction(group, VisualItem.FILLCOLOR, 0));
draw.add(new RepaintAction());
m_vis.putAction("draw", draw);
How would I adapt this code to get, instead of a small square at each point, a thick bar going from th bottom of the graph to the point?
Thanks.
I think I should probably point out how I did this - Stack Overflow is supposed to be a repository too, after all. Earlier in the code was the following:
m_vis.setRendererFactory(new RendererFactory() {
Renderer yAxisRenderer = new AxisRenderer(Constants.LEFT, Constants.TOP);
Renderer xAxisRenderer = new AxisRenderer(Constants.CENTER, Constants.FAR_BOTTOM);
Renderer barRenderer = new ShapeRenderer();
public Renderer getRenderer(VisualItem item) {
return item.isInGroup("yAxis") ? yAxisRenderer :
item.isInGroup("xAxis") ? xAxisRenderer :
barRenderer;
}
});
I extended the shape renderer to always return a rectangle of the correct width and height, and positioned it half a bar to the left of where it was supposed to be. If you want to position your bars in the centre, you need to do that yourself - prefuse won't help you.
m_vis.setRendererFactory(new RendererFactory() {
Renderer yAxisRenderer = new AxisRenderer(Constants.LEFT, Constants.TOP);
Renderer xAxisRenderer = new AxisRenderer(Constants.CENTER, Constants.FAR_BOTTOM);
Renderer barRenderer = new ShapeRenderer() {
protected Shape getRawShape(VisualItem item) {
double x = item.getX();
double y = item.getY();
if (Double.isNaN(x) || Double.isInfinite(x))
x = getInsets().left + axisWidth + totalBarWidth / 2;
if (Double.isNaN(y) || Double.isInfinite(y))
y = 0;
double width = totalBarWidth / (barCount + 1) - barGap;
double height = getHeight() - getInsets().bottom - axisHeight - y;
x -= width / 2;
return rectangle(x, y, width, height);
}
};
public Renderer getRenderer(VisualItem item) {
return item.isInGroup("yAxis") ? yAxisRenderer :
item.isInGroup("xAxis") ? xAxisRenderer :
barRenderer;
}
});

Categories