I have a grid (JTable) that looks like MS Excel's grid. I want to allow the user to resize rows and columns. For the columns I simply used this :
grid.getTableHeader().setResizingAllowed(true);
And for the rows I took the TableRowResizer class from here and which I'm using like this :
new TableRowResizer(grid);
This works fine. However, I've one problem : when resizing a row the row header is not resized too.
Here's how I made the row headers :
AbstractListModel lm = null;
lm = new TableListModel(grid);
final JList list = new JList(lm);
list.setFixedCellWidth(60);
list.setFixedCellHeight(grid.getRowHeight());
list.setCellRenderer(new TableRowHeaderRenderer(grid));
list.setBackground(grid.getTableHeader().getBackground());
scrollPane.setRowHeaderView(list);
Here's the TableRowHeaderRenderer class :
class TableRowHeaderRenderer extends JLabel implements ListCellRenderer {
private JTable table;
public TableRowHeaderRenderer(JTable table)
{
this.table = table;
JTableHeader header = table.getTableHeader();
setOpaque(true);
setBorder(BorderFactory.createEtchedBorder());
setHorizontalAlignment(CENTER);
setForeground(header.getForeground());
setBackground(header.getBackground());
setFont(header.getFont());
}
public Component getListCellRendererComponent(JList list,
Object value, int index, boolean isSelected, boolean cellHasFocus)
{
Color bg = UIManager.getColor("TableHeader.background");
int selectedrow = table.getSelectedRow();
if (selectedrow==index) bg = new Color(107, 142, 35);
setBackground(bg);
setText("" + Grid.getRowName(index));
return this;
}
}
And this is the TableListModelclass :
class TableListModel extends AbstractListModel{
private JTable mytable;
public TableListModel(JTable table) {
super();
mytable = table;
}
public int getSize() {
return mytable.getRowCount();
}
public Object getElementAt(int index) {
return "" + index;
}
}
Check out the Row Number Table. It uses a JTable (instead of a JList) to render the row numbers. Therefore you can keep the row heights in sync with the main table.
However, I can't get the row header to repaint automatically when the row height of the main table is changed since no event is fired when an individual row height is changed. So you will also need to modify the resizing code to look something like:
table.setRowHeight(resizingRow, newHeight);
JScrollPane scrollPane = (JScrollPane)SwingUtilities.getAncestorOfClass(JScrollPane.class, table);
scrollPane.getRowHeader().repaint();
I know this is an old question and have already accepted answer but I find the accepted answer have a performance issue when you implement a resizable row header in which a user can resize the row by just dragging the line in between rows of the header, just like in excel. When dragged outside the header's bound, it begins lagging too much and I can't find any alternative good performance implementation of this so I made my own header and for those who want to implement a resizable row header, you might wanna check this out.
Create an interface that will be use to listen for rows selection in the table.
public interface TableSelectionListener {
public void onRowSelected(int selectedRow);
public void onMultipleRowsSelected(Map<Integer, Integer> rows);
}
make a RowTableHeader class and extend the JComponent, implement the interface you made and some other listeners for the table.
/**
* Initialize this class after setting the table's model
* #author Rene Tajos Jr.
*/
public class TableRowHeader extends JComponent implements ChangeListener, TableModelListener, TableSelectionListener {
private JTable mTable = new JTable();
private final int mRows;
private int mViewportPos = 0;
private final Color mGridColor;
private Color mFontColor = GradeUtils.Colors.darkBlueColor;
private final Color mSelectedRowColor = GradeUtils.Colors.semiCreamWhiteBlueColor;
private final Color mBgColor = new Color(247,245,251);
private int mSelectedRow = -1;
private Map<Integer, Integer> mRowsSelected = new HashMap<>();
private Font mFont = GradeUtils.getDefaultFont(12);
private Map<Integer> resizePoints = new HashMap<>();
private final MouseInputAdapter inputAdapter = new MouseInputAdapter() {
private int mMouseY = -1;
private final int MIN_ROW_HEIGHT = 23;
private int mRowToResize;
private boolean isOnResizingPoint = false;
#Override
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
if (resizePoints.containsKey((int)p.getY())) {
isOnResizingPoint = true;
GradeUtils.setCustomCursor(TableRowHeader.this, GradeUtils.resizeVerticalCursorStr);
return;
}
isOnResizingPoint = false;
setCursor(Cursor.getDefaultCursor());
}
#Override
public void mouseDragged(MouseEvent e) {
if (!isOnResizingPoint)
return;
int y = e.getY();
if (mMouseY != -1) {
int elapse = y - mMouseY;
int oldRowHeight = mTable.getRowHeight(mRowToResize);
int rowHeight = Math.max(MIN_ROW_HEIGHT, oldRowHeight + elapse);
mTable.setRowHeight(mRowToResize, rowHeight);
}
mMouseY = y;
}
#Override
public void mouseReleased(MouseEvent e) {
mMouseY = -1;
isOnResizingPoint = false;
}
#Override
public void mousePressed(MouseEvent e) {
if (isOnResizingPoint) {
mMouseY = e.getY();
// region: get the row point to resize
final Point p = e.getPoint();
p.y += mViewportPos;
mRowToResize = mTable.rowAtPoint(_getRowPoint(p, 5));
// region end
}
}
/**
* Locate the row point to resize
* #param p The event Point
* #param i The number difference of where to locate the row
*/
private Point _getRowPoint(Point p, int i) {
if (!resizePoints.containsKey(p.y -= i)) {
p.y -= i;
return p;
}
return _getRowPoint(p, i-1);
}
};
public TableRowHeader(JTable table) {
mTable = table;
mRows = table.getRowCount();
mGridColor = mTable.getGridColor();
((TajosTable)mTable).addTableSelectionListener(this);
JViewport v = (JViewport) mTable.getParent();
v.addChangeListener(this);
mTable.getModel().addTableModelListener( this );
setPreferredSize(new Dimension(30, HEIGHT));
setOpaque(true);
setBackground(Color.WHITE);
addMouseListener(inputAdapter);
addMouseMotionListener(inputAdapter);
}
/**
* Update the row resize points
*/
private void _updateResizePoints(int y) {
if (!resizePoints.isEmpty())
resizePoints.clear();
int nexPoint = y;
for (int i=0; i<mTable.getRowCount(); i++) {
int resizePointMinThreshold = nexPoint + mTable.getRowHeight(i) - 2;
int resizePointMaxThreshold = nexPoint + mTable.getRowHeight(i) + 2;
for (int j=resizePointMinThreshold; j<=resizePointMaxThreshold; j++)
resizePoints.put(j, j);
nexPoint += mTable.getRowHeight(i);
}
}
#Override
public void setForeground(Color fg) {
mFontColor = fg;
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
int y = -1 - mViewportPos;
_updateResizePoints(y);
// loop in every rows to draw row header
for (int row=0; row<mRows; row++) {
int rowHeight = mTable.getRowHeight(row);
g2d.setColor(mGridColor);
if (row != mRows-1 && row == 0) { // draw row at index 0
// region: draw background
if (row == mSelectedRow || mRowsSelected.containsKey(row))
g2d.setColor(mSelectedRowColor);
else
g2d.setColor(mBgColor);
g2d.fillRect(0, y+1, getPreferredSize().width-1, rowHeight-1);
// region end
// region: draw borders
g2d.setColor(mGridColor);
g2d.drawRect(0, y+1, getPreferredSize().width-1, rowHeight-1);
// region end
} else { // draw the rest of the rows
// region: draw background
if (row == mSelectedRow || mRowsSelected.contains(row))
g2d.setColor(mSelectedRowColor);
else
g2d.setColor(mBgColor);
g2d.fillRect(0, y, getPreferredSize().width-1, rowHeight);
// region end
// region: draw borders
g2d.setColor(mGridColor);
g2d.drawRect(0, y, getPreferredSize().width-1, rowHeight);
// region end
}
// region: draw text with CENTER ALIGNMENT
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setColor(mFontColor);
g2d.setFont(mFont);
String str = String.valueOf(row+1);
FontMetrics fm = getFontMetrics(mFont);
Rectangle2D textRect = fm.getStringBounds(str, g2d);
int textX = ((getPreferredSize().width-1) - (int)textRect.getWidth()) / 2;
int diffY = (rowHeight - (int)textRect.getHeight()) / 2;
int textY = y + (rowHeight - diffY);
g2d.drawString(str, textX, textY - 1);
// region end
y += rowHeight;
}
g2d.dispose();
}
/**
* Implement the ChangeListener
*/
#Override
public void stateChanged(ChangeEvent e)
{
// Keep the view of this row header in sync with the table
JViewport viewport = (JViewport) e.getSource();
mViewportPos = viewport.getViewPosition().y;
}
/**
* Listens for changes in the table
*/
#Override
public void tableChanged(TableModelEvent e)
{
revalidate();
}
/**
* Listens for single row selection
* #param selectedRow The selected row
*/
#Override
public void onRowSelected(int selectedRow) {
mSelectedRow = selectedRow;
if (!mRowsSelected.isEmpty())
mRowsSelected.clear();
revalidate();
repaint();
}
/**
* Listens for multiple row selection
* #param rows The selected rows
*/
#Override
public void onMultipleRowsSelected(Map<Integer, Integer> rows) {
mSelectedRow = -1;
mRowsSelected = rows;
revalidate();
repaint();
}
}
After that, you need to create a class that extends Jtable where you can add a mouselisteners that will listen every time the user selects/multiple selects row/s
public class TajosTable extends JTable {
............
private TableSelectionListener mTableSelectionListener;
private final MouseInputAdapter mouseListener = new MouseInputAdapter() {
private final int TRUE = 1, FALSE = 0;
private int isDraggingMouse;
#Override
public void mousePressed(MouseEvent e) {
final int row = rowAtPoint(e.getPoint());
final int col = columnAtPoint(e.getPoint());
mTableSelectionListener.onRowSelected(row);
}
#Override
public void mouseDragged(MouseEvent e) {
isDraggingMouse = TRUE;
final int[] rowsSelected = getSelectedRows();
final int[] colsSelected = getSelectedColumns();
Map<Integer, Integer> map = new HashMap<>();
for (int row : rowsSelected)
map.put(row, row);
mTableSelectionListener.onMultipleRowsSelected(map);
}
#Override
public void mouseReleased(MouseEvent e) {
isDraggingMouse = FALSE;
}
};
public TajosTable() {
super();
setTableHeader(null);
setColumnSelectionAllowed(true);
// add the mouse listener
addMouseListener(mouseListener);
addMouseMotionListener(mouseListener);
}
............
// and of course create a method that will attach the selection listener
public void addTableSelectionListener(TableSelectionListener lstener) {
mTableSelectionListener = lstener;
}
}
And in your main() {}, always attach this Row Header to the scrollpane AFTER setting the table's model
table.setModel(new YourTableModel(rows, cols));
tableScrollPane.setRowHeaderView(new TableRowHeader(table));
That's it. You can further modify it to suit your needs.
Here is the result, and now when I exited my cursor while resizing a row outside the bounds of my custom made row header. I can't see any lags now.
Here's the result in GIF image
Related
I would a Swing component that almost behave like a JTable for read-only data, but with interactive cells. The table might have hundreds of rows so adding components to the swing tree might not be the right choice.
Currently I'm hacking around the JTable by making the interactive cells editable. This fills hacky and misusing the API, however there's no choice there.
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.Objects;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
public final class InteractiveTableCells extends JPanel {
private InteractiveTableCells() {
super(new BorderLayout());
String wagonsData = "";
Object[][] data = {
{124, wagonsData},
{13, wagonsData},
{78, wagonsData},
{103, wagonsData}
};
var model = new DefaultTableModel(data, new String[] {"Seats", "Train"}) {
#Override
public Class<?> getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
#Override
public boolean isCellEditable(int row, int column) {
return column == 1;
}
};
var table = new JTable(model);
table.setRowHeight(30);
table.setColumnSelectionAllowed(false);
var trainColumn = table.getColumnModel().getColumn(1);
trainColumn.setCellRenderer(new TrainChartPanelRenderer());
trainColumn.setCellEditor(new TrainChartPanelEditor(table));
add(new JScrollPane(table));
setPreferredSize(new Dimension(320, 240));
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
var frame = new JFrame("Read-only Interactive Table Cells");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(new InteractiveTableCells());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
class InteractiveChartPanel extends JPanel {
private final Rectangle wagon1 = new Rectangle(4, 2, 30, 16);
private final Rectangle wagon2 = new Rectangle(4 + 30 + 4, 2, 30, 16);
private final Rectangle wagon3 = new Rectangle(38 + 30 + 4, 2, 30, 16);
private final Rectangle wagon4 = new Rectangle(72 + 30 + 4, 2, 30, 16);
private Rectangle hoveredWagon = null;
protected InteractiveChartPanel() {
super();
setOpaque(true);
addMouseMotionListener(new MouseInputAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
var location = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(location, InteractiveChartPanel.this);
var oldHoveredWagon = hoveredWagon;
if (wagon1.contains(location)) {
hoveredWagon = wagon1;
} else if (wagon2.contains(location)) {
hoveredWagon = wagon2;
} else if (wagon3.contains(location)) {
hoveredWagon = wagon3;
} else if (wagon4.contains(location)) {
hoveredWagon = wagon4;
} else {
hoveredWagon = null;
}
if (!Objects.equals(oldHoveredWagon, hoveredWagon)) {
repaint();
}
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.ORANGE);
g2.fill(wagon1);
g2.fill(wagon2);
g2.fill(wagon3);
g2.fill(wagon4);
if (hoveredWagon != null) {
g2.setColor(Color.ORANGE.darker());
g2.fill(hoveredWagon);
}
}
}
class TrainChartPanelRenderer extends InteractiveChartPanel implements TableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
return this;
}
}
class TrainChartPanelEditor extends AbstractCellEditor implements TableCellEditor {
private final InteractiveChartPanel panel = new InteractiveChartPanel();
protected TrainChartPanelEditor(JTable table) {
table.addMouseMotionListener(new MouseInputAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
int r = table.rowAtPoint(e.getPoint());
int c = table.columnAtPoint(e.getPoint());
if (table.isCellEditable(r, c)
&& (table.getEditingRow() != r || table.getEditingColumn() != c) // avoid flickering, when the mouse mouve over the same cell
) {
// Cancel previous, otherwise editCellAt will invoke stopCellEditing which
// actually get the current value from the editor and set it to the model (see editingStopped)
if (table.isEditing() && r >= 0 && c >= 0) {
table.getCellEditor().cancelCellEditing();
}
table.editCellAt(r, c);
} else {
if (table.isEditing() || r < 0 || c < 0) {
table.getCellEditor().cancelCellEditing();
}
}
}
});
panel.addMouseListener(new MouseInputAdapter() {
#Override
public void mouseExited(MouseEvent e) {
SwingUtilities.invokeLater(TrainChartPanelEditor.this::fireEditingCanceled);
}
});
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
panel.setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
return panel;
}
#Override
public Object getCellEditorValue() {
throw new IllegalStateException("Editing should have been cancelled");
}
}
Bonus, in my case the last row might have longer content, and I'm not quite sure how to adjust the row height dynamically when the JTable is resized for example. And without triggering an infinite loop as calling setRowHeight(row) in the cell renderer can trigger a relayout and then invoke the cell renderer again.
You don’t need to deal with cell editors if you only want a hover highlighting. Just a mouse(motion)listener on the table itself, to update the affected cells, is enough.
For example
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import java.util.List;
public final class InteractiveTableCells {
public static void main(String... args) {
if(!EventQueue.isDispatchThread()) {
EventQueue.invokeLater(InteractiveTableCells::main);
return;
}
var frame = new JFrame("Read-only Interactive Table Cells");
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setContentPane(createContent());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private static Container createContent() {
Integer[] data = { 124, 13, 78, 103 };
var model = new AbstractTableModel() {
#Override
public int getRowCount() {
return data.length;
}
#Override
public int getColumnCount() {
return 2;
}
#Override
public String getColumnName(int column) {
return column == 0? "Seats": "Train";
}
#Override
public Class<?> getColumnClass(int column) {
return column == 0? Integer.class: String.class;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return columnIndex == 0? data[rowIndex]: "";
}
};
var table = new JTable(model);
table.setRowHeight(30);
table.setColumnSelectionAllowed(false);
var trainRenderer = new TrainRenderer();
var defCellRenderer = new DefaultTableCellRenderer();
defCellRenderer.setIcon(trainRenderer);
table.getColumnModel().getColumn(1).setCellRenderer(
(comp, value, selected, focus, row, column) -> {
trainRenderer.paintingActive = row == trainRenderer.activeRow;
return defCellRenderer.getTableCellRendererComponent(
comp, value, selected, focus, row, column);
});
var mouseListener = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
check(table.rowAtPoint(p), table.columnAtPoint(p), p);
}
#Override
public void mouseExited(MouseEvent e) {
check(-1, -1, null);
}
private void check(int r, int c, Point p) {
int lastActive = trainRenderer.activeRow;
if(c != 1) {
trainRenderer.activeRow = -1;
r = -1;
}
if(r < 0 && lastActive < 0) return;
if(r >= 0) {
var rect = table.getCellRect(r, c, false);
p.x -= rect.x;
p.y -= rect.y;
int oldCar = trainRenderer.activeCar;
if(!trainRenderer.check(p, r)) r = -1;
else if(r != lastActive || trainRenderer.activeCar != oldCar)
table.repaint(rect);
}
if(r != lastActive && lastActive >= 0) {
table.repaint(table.getCellRect(lastActive, 1, false));
}
}
};
table.addMouseMotionListener(mouseListener);
table.addMouseListener(mouseListener);
var sp = new JScrollPane(table);
sp.setPreferredSize(new Dimension(320, 240));
return sp;
}
}
class TrainRenderer implements Icon {
private final Rectangle wagon1 = new Rectangle(4, 2, 30, 16);
private final Rectangle wagon2 = new Rectangle(4 + 30 + 4, 2, 30, 16);
private final Rectangle wagon3 = new Rectangle(38 + 30 + 4, 2, 30, 16);
private final Rectangle wagon4 = new Rectangle(72 + 30 + 4, 2, 30, 16);
private final List<Rectangle> allWagons = List.of(wagon1, wagon2, wagon3, wagon4);
int activeRow = -1, activeCar = -1;
boolean paintingActive;
#Override
public void paintIcon(Component c, Graphics g, int x, int y) {
Graphics2D g2 = (Graphics2D)g;
g2.translate(x, y);
for(int i = 0, num = allWagons.size(); i < num; i++) {
g2.setColor(
paintingActive && activeCar == i? Color.ORANGE.darker(): Color.ORANGE);
g2.fill(allWagons.get(i));
}
g2.translate(-x, -y);
}
boolean check(Point p, int row) {
for(int i = 0, num = allWagons.size(); i < num; i++) {
if(allWagons.get(i).contains(p)) {
activeRow = row;
activeCar = i;
return true;
}
}
activeRow = -1;
activeCar = -1;
return false;
}
#Override
public int getIconWidth() {
return wagon4.x + wagon4.width;
}
#Override
public int getIconHeight() {
return wagon4.y + wagon4.height;
}
}
Note that I also removed all unnecessary subclass relationships, compare with Prefer composition over inheritance?
By using the default cell renderer, you get the look&feel’s highlighting, as well as some performance optimizations for free.
When I use normal JPanel initialized inside main instead of extended class. The button is added to a panel without problem and after launch is displayed in a center of a frame (default layout).
I would like to be able to add buttons inside an extended class. This problem occurs in Screen class aswell, where I need a Play Again button or Next Level button. The Screen is class extended by a JPanel too and JButtons are initialized inside the constructor aswell.
I'm not sure if the wrong part is in way of adding the components or in writing a code for a JPanel.
Here is the code:
Main:
public static void main(String[] args) {
// window - class JFrame
theFrame = new JFrame("Brick Breaker");
// game panel initialization
GamePanel gamePanel = new GamePanel(1);
theFrame.getContentPane().add(gamePanel);
// base settings
theFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
theFrame.setLocationRelativeTo(null);
theFrame.setResizable(false);
theFrame.setSize(WIDTH, HEIGHT);
theFrame.setVisible(true);
}
GamePanel Class:
public class GamePanel extends JPanel {
// Fields
boolean running;
boolean clicked = false;
private int rows = 8, colms = 11, N = rows * colms, level; // numbers of rows and columns
private int colmsC, rowsC;
private BufferedImage image;
private Graphics2D g;
private MyMouseMotionListener theMouseListener;
private MouseListener mouseListener;
private int counter = 0;
// entities
Ball theBall;
Paddle thePaddle;
Bricle[] theBricks;
Screen finalScreen;
public GamePanel(int level) {
// buttons
JButton pause = new JButton(" P ");
add(pause);
this.level = level;
init(level);
}
public void init(int level) {
// level logic
this.rowsC = level + rows;
this.colmsC = level + colms;
int count = rowsC * colmsC;
thePaddle = new Paddle();
theBall = new Ball();
theBall.setY(thePaddle.YPOS - thePaddle.getHeight() + 2);
theBall.setX(thePaddle.getX() + thePaddle.getWidth() / 2 - 5);
theBricks = new Bricle[count];
theMouseListener = new MyMouseMotionListener();
addMouseMotionListener(theMouseListener);
mouseListener = new MyMouseListener();
addMouseListener(mouseListener);
// make a canvas
image = new BufferedImage(BBMain.WIDTH, BBMain.HEIGHT, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) image.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// specific Bricks initialized
int k = 0;
for (int row = 0; row < rowsC; row++) {
for (int col = 0; col < colmsC; col++) {
theBricks[k] = new Bricle(row, col);
k++;
}
}
running = true;
}
public void playGame() {
while (running) {
//update
if (clicked) {
update();
}
// draw
draw();
// display
repaint();
// sleep
try {
Thread.sleep(20);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// update loop ( like playGame )
public void update() {
// ball moving
checkCollisions();
theBall.update();
}
public void draw() {
// background
g.setColor(Color.WHITE);
g.fillRect(0,0, BBMain.WIDTH, BBMain.HEIGHT-20);
// the bricks
int k = 0;
for (int row = 0; row < rowsC; row++) {
for (int col = 0; col < colmsC; col++) {
theBricks[k].draw(g, row, col);
k++;
}
}
// the ball and the paddle
theBall.draw(g);
thePaddle.draw(g);
// counter
String countString = new Integer(this.counter).toString();
g.drawString(countString, 20, 20);
// WIN / LOOSE SCREEN
if (this.counter == this.N * 20) {
win();
}
if (theBall.getRect().getY() + theBall.getRect().getHeight() >= BBMain.HEIGHT) {
loose();
}
}
public void paintComponent(Graphics g) {
// retype
Graphics2D g2 = (Graphics2D) g;
// draw image
g2.drawImage(image, 0, 0, BBMain.WIDTH, BBMain.HEIGHT, null);
// dispose
g2.dispose();
}
public void pause() {
this.running = false;
finalScreen = new Screen(this.level, counter);
finalScreen.draw(g,"GAME PAUSED");
}
public void win() {
this.running = false;
finalScreen = new Screen(this.level, counter);
finalScreen.draw(g,"YOU WIN");
}
public void loose () {
this.running = false;
finalScreen = new Screen(this.level, counter);
finalScreen.draw(g,"YOU LOOSE");
}
public void addScore() {
this.counter += 20;
theBall.setDY(theBall.getDY() - 0.001);
}
// Mouse Listeners
private class MyMouseListener implements MouseListener {
#Override
public void mouseClicked(MouseEvent e) {
clicked = true;
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
}
private class MyMouseMotionListener implements MouseMotionListener {
#Override
public void mouseDragged(MouseEvent e) {
}
#Override
public void mouseMoved(MouseEvent e) {
if (clicked)
thePaddle.mouseMoved(e.getX());
}
}
}
I'm coding a game for my final project in Java, our teacher provided us with a Board class that is a component that allows us to place and remove pegs on a virtual game board instead of having to code one ourselves. I'm trying to add Key Binding to the Board component but the action I want performed on key press is happening when I run the program but It won't run when I type a Key.
The board class already has a method for getting the position clicked on the component and I think this might be interfering with my Code but I'm not sure.
This is my game class where I tried to add keybinding
package rpgGame;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
public class RPGGame
{
public static final GameWorld WORLD_MAP = new GameWorld();
public static Board LOCAL_MAP = new Board(20,50);
public static List<Mobile> allMobs = new ArrayList<Mobile>();
public static final Player PLAYER = new Player();
public static int xIndex = ((GameWorld.WORLD_SIZE-1)/2) - (50/2);
public static int yIndex = ((GameWorld.WORLD_SIZE-1)/2) - (20/2);
public static boolean boardUpdate = true;
public enum Direction {RIGHT,LEFT,UP,DOWN}
private static final String MOVE_PLAYER_UP = "move up";
private static final String MOVE_PLAYER_LEFT = "move left";
private static final String MOVE_PLAYER_RIGHT = "move right";
private static final String MOVE_PLAYER_DOWN = "move down";
public static final Thread SYNC_BOARD = new Thread()
{
public synchronized void run()
{
while (boardUpdate)
{
for (int i = 0; i < 50; i++)
{
for (int j = 0; j < 20; j++)
{
if (WORLD_MAP.isOccupied(i+xIndex, j+yIndex))
{
LOCAL_MAP.putPeg(Color.RED, j, i);
System.out.println("Successfully Updated");
}
else
{
LOCAL_MAP.putPeg(Color.GRAY, j,i);
}
}
}
boardUpdate = false;
}
}
};
public RPGGame()
{
generateMobs(200);
placeMobs();
placePlayer();
SYNC_BOARD.run();
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), MOVE_PLAYER_UP);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), MOVE_PLAYER_UP);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), MOVE_PLAYER_LEFT);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), MOVE_PLAYER_LEFT);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), MOVE_PLAYER_RIGHT);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0), MOVE_PLAYER_RIGHT);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), MOVE_PLAYER_DOWN);
LOCAL_MAP.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0), MOVE_PLAYER_DOWN);
LOCAL_MAP.getActionMap().put(MOVE_PLAYER_UP, new MoveAction(Direction.UP));
LOCAL_MAP.getActionMap().put(MOVE_PLAYER_LEFT, new MoveAction(Direction.LEFT));
LOCAL_MAP.getActionMap().put(MOVE_PLAYER_RIGHT, new MoveAction(Direction.RIGHT));
LOCAL_MAP.getActionMap().put(MOVE_PLAYER_DOWN, new MoveAction(Direction.DOWN));
}
public static void main(String[] args)
{
new RPGGame();
}
public static void generateMobs(int numOfMobs)
{
for (int i=0; i<numOfMobs; i++)
{
allMobs.add(new Mobile());
}
}
public static void generateMobs()
{
int numOfMobs = (int)(Math.random()*500);
for (int i=0;i<numOfMobs; i++)
{
allMobs.add(new Mobile());
}
}
public static void placeMobs()
{
for (int i=0; i<allMobs.size(); i++)
{
//i is used as a placeholder value for points until I create a random number generator.
WORLD_MAP.placeCharacter(i, i,allMobs.get(i));
allMobs.get(i).setLocation(i, i);
}
}
public static void placePlayer()
{
WORLD_MAP.placeCharacter(249, 249, PLAYER);
PLAYER.setLocation(249, 249);
}
#SuppressWarnings("serial")
public class MoveAction extends AbstractAction
{
Direction direction;
public MoveAction(Direction direction)
{
if (direction.equals(Direction.RIGHT))
{
int x = PLAYER.getX();
int y = PLAYER.getY();
WORLD_MAP.moveCharacter(x+1, y, x, y);
PLAYER.move(1, 0);
boardUpdate = true;
System.out.println("MOVE RIGHT");
}
if (direction.equals(Direction.LEFT))
{
int x = PLAYER.getX();
int y = PLAYER.getY();
WORLD_MAP.moveCharacter(x, y, x-1, y);
PLAYER.move(-1, 0);
boardUpdate = true;
System.out.println("MOVE LEFT");
}
if (direction.equals(Direction.UP))
{
int x = PLAYER.getX();
int y = PLAYER.getY();
WORLD_MAP.moveCharacter(x, y, x, y+1);
PLAYER.move(0, 1);
boardUpdate = true;
System.out.println("MOVE UP");
}
if (direction.equals(Direction.DOWN))
{
int x = PLAYER.getX();
int y = PLAYER.getY();
WORLD_MAP.moveCharacter(x, y, x, y-1);
PLAYER.move(0, -1);
boardUpdate = true;
System.out.println("MOVE DOWN");
}
}
#Override
public void actionPerformed(ActionEvent e)
{
}
}
}
This is the Board class
package rpgGame;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
/** Board GUI for implementation with various games
* Author: Kirill Levin, Troy Vasiga, Chris Ingram
*/
#SuppressWarnings("serial")
public class Board extends JPanel
{
private static final int X_DIM = 60;
private static final int Y_DIM = 60;
private static final int X_OFFSET = 30;
private static final int Y_OFFSET = 30;
private static final double MIN_SCALE = 0.25;
private static final int GAP = 10;
private static final int FONT_SIZE = 16;
// Grid colours
private static final Color GRID_COLOR_A = new Color(153,255,102);
private static final Color GRID_COLOR_B = new Color(136,255,77);
private Color[][] grid;
private Point lastClick; // How the mouse handling thread communicates
// to the board where the last click occurred
private String message = "";
private int numLines = 0;
private double[][] line = new double[4][100]; // maximum number of lines is 100
private int columns, rows;
private int originalWidth;
private int originalHeight;
private double scale;
/** A constructor to build a 2D board.
*/
public Board (int rows, int columns)
{
super( true );
JFrame boardFrame = new JFrame( "Board game" );
this.columns = columns;
this.rows = rows;
originalWidth = 2*X_OFFSET+X_DIM*columns;
originalHeight = 2*Y_OFFSET+Y_DIM*rows+GAP+FONT_SIZE;
this.setPreferredSize( new Dimension( originalWidth, originalHeight ) );
boardFrame.setResizable(true);
this.grid = new Color[columns][rows];
this.addMouseListener(
new MouseInputAdapter()
{
/** A method that is called when the mouse is clicked
*/
public void mouseClicked(MouseEvent e)
{
int x = (int)e.getPoint().getX();
int y = (int)e.getPoint().getY();
// We need to by synchronized to the parent class so we can wake
// up any threads that might be waiting for us
synchronized(Board.this)
{
int curX = (int)Math.round(X_OFFSET*scale);
int curY = (int)Math.round(Y_OFFSET*scale);
int nextX = (int)Math.round((X_OFFSET+X_DIM*grid.length)*scale);
int nextY = (int)Math.round((Y_OFFSET+Y_DIM*grid[0].length)*scale);
// Subtract one from high end so clicks on the black edge
// don't yield a row or column outside of board because of
// the way the coordinate is calculated.
if (x >= curX && y >= curY && x < nextX && y < nextY)
{
lastClick = new Point(y,x);
// Notify any threads that would be waiting for a mouse click
Board.this.notifyAll() ;
} /* if */
} /* synchronized */
} /* mouseClicked */
} /* anonymous MouseInputAdapater */
);
boardFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
boardFrame.setContentPane( this );
boardFrame.pack();
boardFrame.setVisible(true);
}
/** A constructor to build a 1D board.
*/
public Board (int cols)
{
this(1, cols);
}
private void paintText(Graphics g)
{
g.setColor( this.getBackground() );
g.setFont(new Font(g.getFont().getFontName(), Font.ITALIC+Font.BOLD, (int)(Math.round(FONT_SIZE*scale))));
int x = (int)Math.round(X_OFFSET*scale);
int y = (int)Math.round((Y_OFFSET+Y_DIM*grid[0].length)*scale + GAP ) ;
g.fillRect(x,y, this.getSize().width, (int)Math.round(GAP+FONT_SIZE*scale) );
g.setColor( Color.black );
g.drawString(message, x, y + (int)Math.round(FONT_SIZE*scale));
}
private void paintGrid(Graphics g)
{
for (int i = 0; i < this.grid.length; i++)
{
for (int j = 0; j < this.grid[i].length; j++)
{
if ((i%2 == 0 && j%2 != 0) || (i%2 != 0 && j%2 == 0))
g.setColor(GRID_COLOR_A);
else
g.setColor(GRID_COLOR_B);
int curX = (int)Math.round((X_OFFSET+X_DIM*i)*scale);
int curY = (int)Math.round((Y_OFFSET+Y_DIM*j)*scale);
int nextX = (int)Math.round((X_OFFSET+X_DIM*(i+1))*scale);
int nextY = (int)Math.round((Y_OFFSET+Y_DIM*(j+1))*scale);
int deltaX = nextX-curX;
int deltaY = nextY-curY;
g.fillRect( curX, curY, deltaX, deltaY );
Color curColour = this.grid[i][j];
if (curColour != null) // Draw pegs if they exist
{
g.setColor(curColour);
g.fillOval(curX+deltaX/4, curY+deltaY/4, deltaX/2, deltaY/2);
}
}
}
((Graphics2D) g).setStroke( new BasicStroke(0.5f) );
g.setColor(Color.BLACK);
int curX = (int)Math.round(X_OFFSET*scale);
int curY = (int)Math.round(Y_OFFSET*scale);
int nextX = (int)Math.round((X_OFFSET+X_DIM*grid.length)*scale);
int nextY = (int)Math.round((Y_OFFSET+Y_DIM*grid[0].length)*scale);
g.drawRect(curX, curY, nextX-curX, nextY-curY);
}
private void drawLine(Graphics g)
{
for (int i =0; i < numLines; i++ )
{
((Graphics2D) g).setStroke( new BasicStroke( 5.0f*(float)scale) );
g.drawLine( (int)Math.round((X_OFFSET+X_DIM/2.0+line[0][i]*X_DIM)*scale),
(int)Math.round((Y_OFFSET+Y_DIM/2.0+line[1][i]*Y_DIM)*scale),
(int)Math.round((X_OFFSET+X_DIM/2.0+line[2][i]*X_DIM)*scale),
(int)Math.round((Y_OFFSET+Y_DIM/2.0+line[3][i]*Y_DIM)*scale) );
}
}
/**
* Convert a String to the corresponding Color defaulting to Black
* with an invald input
*/
/*private Color convertColour( String theColour )
{
for( int i=0; i<COLOUR_NAMES.length; i++ )
{
if( COLOUR_NAMES[i].equalsIgnoreCase( theColour ) )
return COLOURS[i];
}
return DEFAULT_COLOUR;
}*/
/** The method that draws everything
*/
public void paintComponent( Graphics g )
{
this.setScale();
this.paintGrid(g);
this.drawLine(g);
this.paintText(g);
}
public void setScale()
{
double width = (0.0+this.getSize().width) / this.originalWidth;
double height = (0.0+this.getSize().height) / this.originalHeight;
this.scale = Math.max( Math.min(width,height), MIN_SCALE );
}
/** Sets the message to be displayed under the board
*/
public void displayMessage(String theMessage)
{
message = theMessage;
this.repaint();
}
/** This method will save the value of the colour of the peg in a specific
* spot. theColour is restricted to
* "yellow", "blue", "cyan", "green", "pink", "white", "red", "orange"
* Otherwise the colour black will be used.
*/
public void putPeg(Color colour, int row, int col)
{
this.grid[col][row] = colour;
this.repaint();
}
/** Same as putPeg above but for 1D boards
*/
public void putPeg(Color colour, int col)
{
this.putPeg(colour, 0, col );
}
/** Remove a peg from the gameboard.
*/
public void removePeg(int row, int col)
{
this.grid[col][row] = null;
repaint();
}
/** Same as removePeg above but for 1D boards
*/
public void removePeg(int col)
{
this.grid[col][0] = null;
repaint();
}
/** Draws a line on the board using the given co-ordinates as endpoints
*/
public void drawLine(double row1, double col1, double row2, double col2)
{
this.line[0][numLines]=col1;
this.line[1][numLines]=row1;
this.line[2][numLines]=col2;
this.line[3][numLines]=row2;
this.numLines++;
repaint();
}
/** Removes one line from a board given the co-ordinates as endpoints
* If there is no such line, nothing happens
* If multiple lines, all copies are removed
*/
public void removeLine(int row1, int col1, int row2, int col2)
{
int curLine = 0;
while (curLine < this.numLines)
{
// Check for either endpoint being specified first in our line table
if ( (line[0][curLine] == col1 && line[1][curLine] == row1 &&
line[2][curLine] == col2 && line[3][curLine] == row2) ||
(line[2][curLine] == col1 && line[3][curLine] == row1 &&
line[0][curLine] == col2 && line[1][curLine] == row2) )
{
// found a matching line: overwrite with the last one
numLines--;
line[0][curLine] = line[0][numLines];
line[1][curLine] = line[1][numLines];
line[2][curLine] = line[2][numLines];
line[3][curLine] = line[3][numLines];
curLine--; // perhaps the one we copied is also a match
}
curLine++;
}
repaint();
}
/** Waits for user to click somewhere and then returns the click.
*/
public Point getClick()
{
Point returnedClick = null;
synchronized(this) {
lastClick = null;
while (lastClick == null)
{
try {
this.wait();
} catch(Exception e) {
// We'll never call Thread.interrupt(), so just consider
// this an error.
e.printStackTrace();
System.exit(-1) ;
} /* try */
}
int x = (int)Math.floor((lastClick.getY()-X_OFFSET*scale)/X_DIM/scale);
int y = (int)Math.floor((lastClick.getX()-Y_OFFSET*scale)/Y_DIM/scale);
// Put this into a new object to avoid a possible race.
returnedClick = new Point(x,y);
}
return returnedClick;
}
/** Same as getClick above but for 1D boards
*/
public double getPosition()
{
return this.getClick().getY();
}
public int getColumns()
{
return this.columns;
}
public int getRows()
{
return this.rows;
}
}
You're shooting yourself in the foot with that thread code -- you're calling run() not start() on it
SYNC_BOARD.run();
This will run on the Swing event thread and risks completely freezing your GUI.
As a general rule, you should almost never extend Thread but rather implement Runnable, but regardless, don't use that Thread code -- Instead use a Swing Timer since your code has no breaks in it, no Thread.sleeps and it will make your CPU awfully busy, and the Swing Timer will help make sure that your code obeys Swing threading rules.
Also your MoveAction is wrong. Most of that code should go in the actionPerformed method. The constructor should just set the direction field and that's it.
Something like:
#SuppressWarnings("serial")
public class MoveAction extends AbstractAction {
Direction direction;
public MoveAction(Direction direction) {
// this is the only code the constructor should have!
this.direction = direction;
}
#Override
public void actionPerformed(ActionEvent e) {
// use direction to help make move in here
}
}
Understand that this is likely causing some major problems, since the constructor is called on program creation (hence your key bindings "work" when the program starts), but it's the actionPerformed that actually gets called when the right key is pressed.
First off, please accept my apologies if this question is basic, I mainly have knowledge of C# but am forced to use Java for this particular project!
I'm trying to implement a GUI to display an occupancy grid based on robot sensor data. The occupancy grid will be quite large, perhaps up to 1500x1500 grid squares representing real-world area of around 10cm2 per grid cell.
Each grid square will simply store an Enumerable status, for example:
Unknown
Unoccupied
Occupied
Robot
I would simply like to find the best way to render this as a grid, using different colour squares to depict different grid cell status'.
I have implemented a naive, basic algorithm to draw squares and grid lines, however it performs VERY badly on larger occupancy grids. Other code in the class redraws the window every 0.5s as new sensor data is collected, I suspect the reason for the very poor performance is the fact that i am rendering EVERY cell EVERY time. Is there an easy way i can selectively render these cells, should I wrap each cell in an observable class?
My current implementation:
#Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
int width = getSize().width;
int height = getSize().height;
int rowHeight = height / (rows);
int colWidth = width / (columns);
//Draw Squares
for (int row = 0; row < rows; row++) {
for (int col = 0; col < columns; col++) {
switch (this.grid[row][col]) {
case Unexplored:
g.setColor(Color.LIGHT_GRAY);
break;
case Empty:
g.setColor(Color.WHITE);
break;
case Occupied:
g.setColor(Color.BLACK);
break;
case Robot:
g.setColor(Color.RED);
break;
}
g.drawRect(col * colWidth, height - ((row + 1) * rowHeight), colWidth, rowHeight);
g.fillRect(col * colWidth, height - ((row + 1) * rowHeight), colWidth, rowHeight);
}
}
int k;
if (outline) {
g.setColor(Color.black);
for (k = 0; k < rows; k++) {
g.drawLine(0, k * rowHeight, width, k * rowHeight);
}
for (k = 0; k < columns; k++) {
g.drawLine(k * colWidth, 0, k * colWidth, height);
}
}
}
private void setRefresh() {
Action updateUI = new AbstractAction() {
boolean shouldDraw = false;
public void actionPerformed(ActionEvent e) {
repaint();
}
};
new Timer(updateRate, updateUI).start();
}
Please help! Thanks in advance.
ROS - robot operating system from willowgarage has an occupancygrid implementation in java: http://code.google.com/p/rosjava/source/browse/android_honeycomb_mr2/src/org/ros/android/views/map/OccupancyGrid.java?spec=svn.android.88c9f4af5d62b5115bfee9e4719472c4f6898665&repo=android&name=88c9f4af5d&r=88c9f4af5d62b5115bfee9e4719472c4f6898665
You may use it or get ideas from it.
You need to respect the clip rectangle when painting (assuming your grid is in a JScrollPane) and use JComponent#repaint(Rectangle) appropriately.
See this sample program (although it related to loading the value of a cell lazily, it also has the clip bounds painting):
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.*;
public class TilePainter extends JPanel implements Scrollable {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Tiles");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(new TilePainter()));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private final int TILE_SIZE = 50;
private final int TILE_COUNT = 1000;
private final int visibleTiles = 10;
private final boolean[][] loaded;
private final boolean[][] loading;
private final Random random;
public TilePainter() {
setPreferredSize(new Dimension(
TILE_SIZE * TILE_COUNT, TILE_SIZE * TILE_COUNT));
loaded = new boolean[TILE_COUNT][TILE_COUNT];
loading = new boolean[TILE_COUNT][TILE_COUNT];
random = new Random();
}
public boolean getTile(final int x, final int y) {
boolean canPaint = loaded[x][y];
if(!canPaint && !loading[x][y]) {
loading[x][y] = true;
Timer timer = new Timer(random.nextInt(500),
new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
loaded[x][y] = true;
repaint(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
}
});
timer.setRepeats(false);
timer.start();
}
return canPaint;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle clip = g.getClipBounds();
int startX = clip.x - (clip.x % TILE_SIZE);
int startY = clip.y - (clip.y % TILE_SIZE);
for(int x = startX; x < clip.x + clip.width; x += TILE_SIZE) {
for(int y = startY; y < clip.y + clip.height; y += TILE_SIZE) {
if(getTile(x / TILE_SIZE, y / TILE_SIZE)) {
g.setColor(Color.GREEN);
}
else {
g.setColor(Color.RED);
}
g.fillRect(x, y, TILE_SIZE, TILE_SIZE);
}
}
}
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(visibleTiles * TILE_SIZE, visibleTiles * TILE_SIZE);
}
#Override
public int getScrollableBlockIncrement(
Rectangle visibleRect, int orientation, int direction) {
return TILE_SIZE * Math.max(1, visibleTiles - 1);
}
#Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
#Override
public boolean getScrollableTracksViewportWidth() {
return false;
}
#Override
public int getScrollableUnitIncrement(
Rectangle visibleRect, int orientation, int direction) {
return TILE_SIZE;
}
}
Creating rectangles is probably too slow. Instead, why don't you create a bitmap image, each pixel being a cell of the grid, you can then scale it to whatever size you want.
The following class takes a matrix of integers, and saves it to a bitmap file.
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class BMP {
private final static int BMP_CODE = 19778;
byte [] bytes;
public void saveBMP(String filename, int [][] rgbValues){
try {
FileOutputStream fos = new FileOutputStream(new File(filename));
bytes = new byte[54 + 3*rgbValues.length*rgbValues[0].length + getPadding(rgbValues[0].length)*rgbValues.length];
saveFileHeader();
saveInfoHeader(rgbValues.length, rgbValues[0].length);
saveRgbQuad();
saveBitmapData(rgbValues);
fos.write(bytes);
fos.close();
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
}
private void saveFileHeader() {
byte[]a=intToByteCouple(BMP_CODE);
bytes[0]=a[1];
bytes[1]=a[0];
a=intToFourBytes(bytes.length);
bytes[5]=a[0];
bytes[4]=a[1];
bytes[3]=a[2];
bytes[2]=a[3];
//data offset
bytes[10]=54;
}
private void saveInfoHeader(int height, int width) {
bytes[14]=40;
byte[]a=intToFourBytes(width);
bytes[22]=a[3];
bytes[23]=a[2];
bytes[24]=a[1];
bytes[25]=a[0];
a=intToFourBytes(height);
bytes[18]=a[3];
bytes[19]=a[2];
bytes[20]=a[1];
bytes[21]=a[0];
bytes[26]=1;
bytes[28]=24;
}
private void saveRgbQuad() {
}
private void saveBitmapData(int[][]rgbValues) {
int i;
for(i=0;i<rgbValues.length;i++){
writeLine(i, rgbValues);
}
}
private void writeLine(int row, int [][] rgbValues) {
final int offset=54;
final int rowLength=rgbValues[row].length;
final int padding = getPadding(rgbValues[0].length);
int i;
for(i=0;i<rowLength;i++){
int rgb=rgbValues[row][i];
int temp=offset + 3*(i+rowLength*row) + row*padding;
bytes[temp] = (byte) (rgb>>16);
bytes[temp +1] = (byte) (rgb>>8);
bytes[temp +2] = (byte) rgb;
}
i--;
int temp=offset + 3*(i+rowLength*row) + row*padding+3;
for(int j=0;j<padding;j++)
bytes[temp +j]=0;
}
private byte[] intToByteCouple(int x){
byte [] array = new byte[2];
array[1]=(byte) x;
array[0]=(byte) (x>>8);
return array;
}
private byte[] intToFourBytes(int x){
byte [] array = new byte[4];
array[3]=(byte) x;
array[2]=(byte) (x>>8);
array[1]=(byte) (x>>16);
array[0]=(byte) (x>>24);
return array;
}
private int getPadding(int rowLength){
int padding = (3*rowLength)%4;
if(padding!=0)
padding=4-padding;
return padding;
}
}
With that class, you can simply do:
new BMP().saveBMP(fieName, myOccupancyMatrix);
Generating the matrix of integers (myOccupancyMatrix) is easy. A simple trick to avoid the Switch statement is assigning the color values to your Occupancy enum:
public enum Occupancy {
Unexplored(0x333333), Empty(0xFFFFFF), Occupied(0x000000), Robot(0xFF0000);
}
Once you save it do disk, the BMP can be shown in an applet and scaled easily:
public class Form1 extends JApplet {
public void paint(Graphics g) {
Image i = ImageIO.read(new URL(getCodeBase(), "fileName.bmp"));
g.drawImage(i,0,0,WIDTH,HEIGHT,Color.White,null);
}
}
Hope this helps!
Rendering even a subset of 2,250,000 cells is not a trivial undertaking. Two patterns you'll need are Model-View-Controller, discussed here, and flyweight, for which JTable may be useful.
I have a JComboBox and would like to have a separator in the list of elements. How do I do this in Java?
A sample scenario where this would come in handy is when making a combobox for font-family-selection; similar to the font-family-selection-control in Word and Excel. In this case I would like to show the most-used-fonts at the top, then a separator and finally all font-families below the separator in alphabetical order.
Can anyone help me with how to do this or is this not possible in Java?
There is a pretty short tutorial with an example that shows how to use a custom ListCellRenderer on java2s
http://www.java2s.com/Code/Java/Swing-Components/BlockComboBoxExample.htm
Basically it involves inserting a known placeholder in your list model and when you detect the placeholder in the ListCellRenderer you return an instance of 'new JSeparator(JSeparator.HORIZONTAL)'
By the time I wrote and tested the code below, you probably got lot of better answers...
I don't mind as I enjoyed the experiment/learning (still a bit green on the Swing front).
[EDIT] Three years later, I am a bit less green, and I took in account the valid remarks of bobndrew. I have no problem with the key navigation that just works (perhaps it was a JVM version issue?). I improved the renderer to show highlight, though. And I use a better demo code. The accepted answer is probably better (more standard), mine is probably more flexible if you want a custom separator...
The base idea is to use a renderer for the items of the combo box. For most items, it is a simple JLabel with the text of the item. For the last recent/most used item, I decorate the JLabel with a custom border drawing a line on its bottom.
import java.awt.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class TwoPartsComboBox extends JComboBox
{
private int m_lastFirstPartIndex;
public TwoPartsComboBox(String[] itemsFirstPart, String[] itemsSecondPart)
{
super(itemsFirstPart);
m_lastFirstPartIndex = itemsFirstPart.length - 1;
for (int i = 0; i < itemsSecondPart.length; i++)
{
insertItemAt(itemsSecondPart[i], i);
}
setRenderer(new JLRenderer());
}
protected class JLRenderer extends JLabel implements ListCellRenderer
{
private JLabel m_lastFirstPart;
public JLRenderer()
{
m_lastFirstPart = new JLabel();
m_lastFirstPart.setBorder(new BottomLineBorder());
// m_lastFirstPart.setBorder(new BottomLineBorder(10, Color.BLUE));
}
#Override
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
{
if (value == null)
{
value = "Select an option";
}
JLabel label = this;
if (index == m_lastFirstPartIndex)
{
label = m_lastFirstPart;
}
label.setText(value.toString());
label.setBackground(isSelected ? list.getSelectionBackground() : list.getBackground());
label.setForeground(isSelected ? list.getSelectionForeground() : list.getForeground());
label.setOpaque(true);
return label;
}
}
}
Separator class, can be thick, with custom color, etc.
import java.awt.*;
import javax.swing.border.AbstractBorder;
/**
* Draws a line at the bottom only.
* Useful for making a separator in combo box, for example.
*/
#SuppressWarnings("serial")
class BottomLineBorder extends AbstractBorder
{
private int m_thickness;
private Color m_color;
BottomLineBorder()
{
this(1, Color.BLACK);
}
BottomLineBorder(Color color)
{
this(1, color);
}
BottomLineBorder(int thickness, Color color)
{
m_thickness = thickness;
m_color = color;
}
#Override
public void paintBorder(Component c, Graphics g,
int x, int y, int width, int height)
{
Graphics copy = g.create();
if (copy != null)
{
try
{
copy.translate(x, y);
copy.setColor(m_color);
copy.fillRect(0, height - m_thickness, width - 1, height - 1);
}
finally
{
copy.dispose();
}
}
}
#Override
public boolean isBorderOpaque()
{
return true;
}
#Override
public Insets getBorderInsets(Component c)
{
return new Insets(0, 0, m_thickness, 0);
}
#Override
public Insets getBorderInsets(Component c, Insets i)
{
i.left = i.top = i.right = 0;
i.bottom = m_thickness;
return i;
}
}
Test class:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class TwoPartsComboBoxDemo extends JFrame
{
private TwoPartsComboBox m_combo;
public TwoPartsComboBoxDemo()
{
Container cont = getContentPane();
cont.setLayout(new FlowLayout());
cont.add(new JLabel("Data: ")) ;
String[] itemsRecent = new String[] { "ichi", "ni", "san" };
String[] itemsOther = new String[] { "one", "two", "three" };
m_combo = new TwoPartsComboBox(itemsRecent, itemsOther);
m_combo.setSelectedIndex(-1);
cont.add(m_combo);
m_combo.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
String si = (String) m_combo.getSelectedItem();
System.out.println(si == null ? "No item selected" : si.toString());
}
});
// Reference, to check we have similar behavior to standard combo
JComboBox combo = new JComboBox(itemsRecent);
cont.add(combo);
}
/**
* Start the demo.
*
* #param args the command line arguments
*/
public static void main(String[] args)
{
// turn bold fonts off in metal
UIManager.put("swing.boldMetal", Boolean.FALSE);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
JFrame demoFrame = new TwoPartsComboBoxDemo();
demoFrame.setTitle("Test GUI");
demoFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
demoFrame.setSize(400, 100);
demoFrame.setVisible(true);
}
});
}
}
You can use a custom ListCellRenderer which would draw the separator items differently. See docs and a small tutorial.
Try adding this renderer. Just supply a list of index values that you want the separator to be above.
private class SeperatorComboRenderer extends DefaultListCellRenderer
{
private final float SEPARATOR_THICKNESS = 1.0f;
private final float SPACE_TOP = 2.0f;
private final float SPACE_BOTTOM = 2.0f;
private final Color SEPARATOR_COLOR = Color.DARK_GRAY;
private final List<Integer> marks;
private boolean mark;
private boolean top;
public SeperatorComboRenderer(List<Integer> marks)
{
this.marks = marks;
}
#Override
public Component getListCellRendererComponent(JList list, Object object, int index, boolean isSelected, boolean hasFocus)
{
super.getListCellRendererComponent(list, object, index, isSelected, hasFocus);
top = false;
mark = false;
marks.forEach((idx) ->
{
if(index - 1 == idx)
top = true;
if(index == idx)
mark = true;
});
return this;
}
#Override
protected void paintComponent(Graphics g)
{
if(mark)
g.translate(0, (int)(SEPARATOR_THICKNESS + SPACE_BOTTOM));
Graphics2D g2 = (Graphics2D)g;
super.paintComponent(g);
if(mark)
{
g2.setColor(SEPARATOR_COLOR);
g2.setStroke(new BasicStroke(SEPARATOR_THICKNESS));
g2.drawLine(0, 0, getWidth(), 0);
}
}
#Override
public Dimension getPreferredSize()
{
Dimension pf = super.getPreferredSize();
double height = pf.getHeight();
if(top) height += SPACE_TOP;
else if(mark) height += SEPARATOR_THICKNESS + SPACE_BOTTOM;
return new Dimension((int)pf.getWidth(), (int)height);
}
}