Dynamically Increase JTable row height when editing, and decrease when finish edit - java

I am writing a small POS application, which shows a JTable with ticket information inside its cells. CellRenderer is a class which extends JPanel and implements TableCellRenderer, and contains a few JTextFields showing basic information (quantity, descripcion, price).
Also, I have another class extending JPanel and implementing TableCellEditor, which is used as CellEditor. This class contains more JTextFields and also a few jButtons.
What I need is simple: when I edit any cell by clicking with the mouse (or touching the screen, which is, as far as I know, the same event), dynamically increase the height of the cell I'm going to edit, so it can show all the components inside the editor. And when I finish editing, return cell height to its previous value.
Any idea about doing it?
Thanks in advance. :-)

CellRenderer is a class which extends JPanel and implements TableCellRenderer, and contains a few JTextFields showing basic information (quantity, descripcion, price). Also, I have another class extending JPanel and implementing TableCellEditor, which is used as CellEditor. This class contains more JTextFields and also a few jButtons.
better could be to create popup window (JDialog) based on event from JPopupMenu,
Dynamically Increase JTable row height when editing, and decrease when finish edit
don't confused users and wrong concept could be caused by jumping JTables row on the screen
What I need is simple: when I edit any cell by clicking with the mouse (or touching the screen, which is, as far as I know, the same event), dynamically increase the height of the cell I'm going to edit, so it can show all the components inside the editor. And when I finish editing, return cell height to its previous value.
don't do that, but have to override, is possible by
DefaultCellEditor#setClickCountToStart(int) for TableCellEditor
start, stop and cancelEdit for CellEditor
have to notify or re_Layout JTable, the same on stop and cancelEdit

Not an answer to how-to-adjust-rowHeights, but for an alternative mentioned in my comment: "oversize" the editorComponent only instead of updating the complete rowHeight (which I think would be too irritating to users, but up to you to decide, of course :)
// fake a bigger editing component
JTextField oversized = new JTextField() {
#Override
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.height *= 5;
return dim;
}
};
TableCellEditor editor = new DefaultCellEditor(oversized);
JTable table = new JTable(new AncientSwingTeam()) {
#Override
public boolean editCellAt(int row, int column, EventObject e) {
boolean result = super.editCellAt(row, column, e);
if (result) {
// adjust cell bounds of the editing component
Rectangle bounds = getEditorComponent().getBounds();
bounds.height = getEditorComponent().getPreferredSize().height;
getEditorComponent().setBounds(bounds);
}
return result;
}
#Override
public void removeEditor() {
int editingColumn = getEditingColumn();
super.removeEditor();
if (editingColumn >= 0) {
// cleanup
repaint();
}
}
};
table.getColumnModel().getColumn(0).setCellEditor(editor);

Didn't try it, but I would say implementing MouseListener's mouseClicked() method is the way to go. Keep track of whether the cells height was already increased, and change the height accordingly.
Since MouseListener is an interface, CellRenderer could implement this interface too, keeping all cell-behavior in one class.

Related

Scroll to selected row correctly for JTable

I'm using a JTable to display data. When a row is selected programmatically, it should (automatically) scroll that row into view so that it is the first row on top. For this I have the following method:
private static void scrollToSelectedRow(JTable table)
{
JViewport viewport = (JViewport) table.getParent();
Rectangle cellRectangle = table.getCellRect(table.getSelectedRow(), 0, true);
Rectangle visibleRectangle = viewport.getVisibleRect();
table.scrollRectToVisible(new Rectangle(cellRectangle.x, cellRectangle.y, (int) visibleRectangle.getWidth(), (int) visibleRectangle.getHeight()));
}
The problem is that calling this may or may not scroll correctly, e.g. the selected row may be on top or not in a non-deterministic way. In order to fix this, I tried calling the method twice with a delay in-between as well as repainting it:
public static void setSelectedRow(JTable table, int rowIndex, int columnIndex)
{
table.setRowSelectionInterval(rowIndex, columnIndex);
scrollToSelectedRow(table);
try
{
Thread.sleep(100);
} catch (InterruptedException exception)
{
exception.printStackTrace();
}
scrollToSelectedRow(table);
table.repaint();
}
This works a lot better but still sometimes glitches the table columns out since apparently it is not smart to halt the EDT while it is updating. The repaint() call appears to prevent the rows from "overlapping" in a similar fashion. The graphical glitches can be fixed easily by moving the mouse over the affected rows/columns though but it should not happen.
How can I gracefully perform the scrolling without waiting and without having graphical glitches?
Code looks reasonable. Normally the trick is to make sure the Rectangle width/height is the size of the viewport to force the scroll.
Two thoughts.
Get rid of the Thread.sleep(). all that will do is prevent the GUI from repainting itself for 100ms.
Try wrapping the scrollRectToVisible(...) in a SwingUtlities.invokeLater(...).
If point 2 doesn't help then post a proper [mcve] that demonstrates the problem.

ListCellRenderer setting all rows to the same color

My intention is to use the ListCellRenderer in order to highlight red cells that contain links that have been visited(or clicked) and green those which have not been visited this works partially but not quite. It seems that the renderer works as far as it concerns marking the cells red. If I however add more rows, they come all red colored thereafter. In addition if I mark two cells that are not adjacent then it marks them all red as well.
I have a class Feed, where I initially had a boolean variable, but I have modified the code so that the m_isRead variable is in the listModel here is the constructor:
public Feed (URL url, String urlName) {
this.m_urlName = urlName;
this.m_observers = new ArrayList<Observer>();
this.m_isRead = isRead;
}
Now this instance variable is set to false in the listModel Class which is the one that contains the renderer.
m_isRead = false.
When using the ListCellRenderer which I now have adjusted so that it does not require this method:
m_feeds.get(index).getM_urlName();
I proceed as follows:
class MyCellRenderer extends JLabel implements ListCellRenderer {
public MyCellRenderer() {
setOpaque(true);
}
public Component getListCellRendererComponent(JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
setText(value.toString());
Color background = Color.GREEN;
Color foreground = Color.BLACK;
//find out if the specific has been read or not
if (m_feeds.get(index).isM_isRead() == true) {
background = Color.RED;
foreground = Color.WHITE;
} else {
background = Color.GREEN;
foreground = Color.BLACK;
};
setBackground(background);
setForeground(foreground);
return this;
}
}
Then I have another inner class with a method which I use to get the selected item, at that point I set m_isRead to true (to read) this is now independent from the Feed class and the code which related to it has been commented out:
public class ChangeSelectedIndex implements ListSelectionListener {
#Override
public void valueChanged(ListSelectionEvent e) {
for (int i = 0; i < m_listModel.size(); i++) {
if (m_listModel.getElementAt(i) != null) {
m_urlName = m_list.getSelectedValue();
initiateParsing();
m_updateFeedButton.setEnabled(true);
// TODO fix behavior for cell renderer
//this sets the value of the feed being clicked to true
// m_feeds.get(i).setM_isRead(true);
m_isRead = true;
}
}// end for
}
}
Now the result is the same, if I add the rows they are green and that is correct, if I click on each row each turns read provided that I have clicked the adjacent rows to the first one I click but if I, for example, have four rows and I click the first row and the last row, all the rows, including those in between (which I have not clicked) turn red. Likewise, if I add new rows they come in red. That is if I click even one of those rows then the ones I add thereafter will be red.
Can anybody help?
Thank you in advance,
Cheers
After a while thinking about it I have concluded that there was nothing wrong my original cell renderer, it has had to do with the list model itself. The JList simply did not support multiple NON contiguous selection without clicking the Ctrl button right out of the box. This is what triggered further searching on my side on how to emulate the Ctrl click down; which I found here on answer number 8 (working code):
Individual and not continuous JTable's cell selection
The interesting here is adding the mouse event to the list. This mouse event emulates a Ctrl down event, which the ListSelectionModel which is used by JList as well as JTable is set to MULTPLE_SELECTION_INTERVAL it behaves as desired. That is, the user now able to click on whatever feed, even if it is not contiguous, and it will color the desired feed or feeds without coloring whatever unclicked feed may lay in between.
As for the renderer, it would suffice to use the isSelected parameter which comes in through with its method getListCellRenderer(). However, in my case, what I had done has the same effect with the addition that I was using an array to add all the statuses of the feeds, meaning, read or unread. Proceeding this way, I had in mind that if I closed the program and save the feed list, including its isRead parameter set to either true or false, then later on upon retrieving the feed list, the same feed status would be restored from, for example, a file, or at least that is what I had in mind.

Java Swing mouseDragged callback speed

I have a question regarding the callback speed of the mouseDragged message of the MouseMotionListener in Java Swing. This post is sort of related but it's not entirely the same so I started a question of my own.
I'm making a small in-house application with no eye on commercial distribution that is basically a digitalized TCG (Trading Card Game) emulator. For any of you familiar with MtG (Magic the Gathering), you might've heard from such a similar program. I'm trying to create something that looks sort of like this, but less fancy.
My GUI consists of a JFrame with menu and then some panels containing various buttons and labels, but I'll only go over the relevent parts to explain my problem.
In essence, I'm using a vertical split JSplitPane with a JPanel on the left, with in that a JScrollPane with a JList in it, which represents at any time the cards in your hand that you can play. On the right side of the split, I have a JLayeredPane with a background image in the DEFAULT_LAYER (subclass of JPanel that overrides the draw function to add an image) and, on various layers above the PALETTE_LAYER, I display the cards that are in play (gathered in an ArrayList) by means of custom CardPanels (another subclass of JPanel that illustrates a card). The entire JLayeredPane is thus a representation of the table in front of you with all the cards you've already played.
I first started by adding a MouseListener and a MouseMotionListener to the JLayeredPane to pick up events, allowing me to register a mouse press, check if this was above a card, then use the mouse dragged event to move the card around and finally mouse release to place it back . This all works perfectly fine and if I add logging information I can see the mouseDragged callback function is called often, allowing for a visually fast dragging motion without lag.
Today I decided to add functionality to allow the user to drag a card from his hand to the "table" (instead of double clicking on the card in the JList), so I added the appropriate listeners to the JList along with filling in some functions like MousePressed and MouseReleased. On a mouse press, I check what card from the list was clicked, I lock the list, create a custom CardPanel (but don't add it anywhere yet, I just allocate and initiate it!) and set a flag. In mouse dragged, I check if this flag is set. If it is, I check where the cursor is. If it is anywhere above the JLayeredPane, I add the CardPanel to the DRAG_LAYER and set another flag. If this second flag is set in successive calls to mouse dragged, I don't add the panel again but I just change the location. This functionality is practically the same as the one in my previous mouse dragged callback. On mouse release, I unlock the list and add the CardPanel on the correct layer in the JLayeredPane.
Everything is working as intended so I'm pretty sure my code is okay, but there is just one slight issue:
When dragging a card from the list to the layered pane (instead of from the layered pane to the layered pane), I notice the mouseDragged callback is called at a pretty low frequency by the JList (approx 10 times per second), introducing some visually disturbing lag (compared to approx 30 times per second in the first case of dragging).
I'm going to add some code snippets as to clarify my problem but I'm afraid adding all the code to allow you to run it yourself would be serious overkill.
The main question in this post is: does anybody know why the mouseDragged is called faster by one MouseMotionListener than by another MouseMotionListener? The listener to the JLayeredPane component makes fast successive calls, the listener to the JList calls significantly slower.
Note: I'm developing in Netbeans and I'm using the built-in graphical Swing Interface Builder. I'm using a JFrame form as my main class.
public class MyFrame extends JFrame{
...
protected JLayeredPane layeredPane;
protected JList cardsInHandList;
...
...
protected ArrayList<String> cardsInHand;
...
private void attachListeners(){
layeredPane.addMouseListener(new MouseAdapter(){
public void MousePressed(MouseEvent e){
// set a flag, start a drag
}
public void MouseReleased(MouseEvent e){
// unset a flag, stop a drag
}
});
layeredPane.addMouseMotionListener(new MouseMotionAdapter(){
public void MouseDragged(MouseEvent e){
// drag the card around
// gets called a lot!
// actual code:
if (e.getButton() == MouseEvent.BUTTON1) {
if (!dragging) return; // the flag
int x = e.getX() - 10;
int y = e.getY() - 10;
// snap to grid
x /= GRIDX;
x *= GRIDX;
y /= GRIDY;
y *= GRIDY;
// redraw the card at its new location
draggedCard.setLocation(x, y);
}
}
});
cardsInHandList.addMouseListener(new MouseAdapter(){
public void MousePressed(MouseEvent e){
// set a flag, start a drag
}
public void MouseReleased(MouseEvent e){
// unset a flag, stop a drag
}
});
cardsInHandList.addMouseMotionListener(new MouseMotionAdapter(){
public void MouseDragged(MouseEvent evt){
// check cursor location, drag if within bounds of layeredPane
// gets called a whole lot less!! _Why?_
// actual code:
if (!draggingFromHand) return; // the flag
// check location of cursor with own method (contains() didn't work for me)
if (isCursorAtPointAboveLayeredPane(evt.getLocationOnScreen())) {
// calculate where and snap to grid
int x = (int) (evt.getLocationOnScreen().getX() - layeredPane.getLocationOnScreen().getX())-10;
int y = (int) (evt.getLocationOnScreen().getY() - layeredPane.getLocationOnScreen().getY())-10;
// snap to grid
x /= GRIDX;
x *= GRIDX;
y /= GRIDY;
y *= GRIDY;
if(!draggingFromHandCardPanelAdded){
layeredPane.add(draggingFromHandCardPanel, JLayeredPane.DRAG_LAYER);
draggingFromHandCardPanelAdded = true;
} else {
draggingFromHandCardPanel.setLocation(x,y);
}
}
}
});
}
I'll try to build a short runnable example reproducing the problem and then attach the code somewhere but right now I got to skoot.
Thanks in advance
PS: I am aware that there is another way to drag in Java, involving TransferHandlers and all that but it just seems like too much hassle and it isn't an actual answer to my question of how come the one callback seems to be called more than the other, so please don't tell me to use that instead.
Once you drag outside the list, Java start generating synthetic mouse events for the list, which might be the cause. See the javadoc for JComponent#setAutoscrolls(boolean).
You might get better results using a global event listener, see
http://tips4java.wordpress.com/2009/08/30/global-event-listeners/

Get shown component in JScrollPane

I have a JScrollPane containing a JPanel. I fill this JPanel with many buttons.
Is there any possibility to get the currently shown buttons?
I know I can access the children of a JPanel via jpanel.getComponents() but those are all components in this pane; I want only the ones that are currently on screen.
As already commented to #mKorbel's answer:
it's correct that you need the child bounds
it's correct that you need to intersect those bounds with "something"
it's wrong that you need the containing viewport (nor the scrollpane)
JComponents have an API to get their currently visible part independently of how/where exactly they are currently shown, so the "something" is the JComponent's visibleRect:
Rectangle visibleRect = myPanel.getVisibleRect();
for (Component child : myPanel.getComponents()) {
Rectangle childBounds = child.getBounds();
if (childBounds.intersects(visibleRect)) {
// do stuff
}
}
I assume that this container is already visible on the screen, then I suggest
1) to extract JViewPort from JScrollPane,
2) addChangeListener to JViewPort
3) each visible JComponent(s) returns Rectangle
4) and Rectangle#intersects returns Boolean value if is JComponent(s) visible or not in JViewPort
How about asking the components if they're visible:
for ( Component component : jpanel.getComponents() ) {
if ( component instanceof JButton && component.isShowing() ) {
// We've found a button that is showing...
}
}
Component#isShowing()
scrollPane.getViewport().getView()
scrollPane.getViewport().getViewRect()

Java Swing - Background of a JPanel

I want to design a JPanel which should have the color coding as shown in the following diagram:
(source: compendiumblog.com)
How can I code the colors of a JPanel. What I think is that add 5 JPanels (for 5 blocks shown above) on a main JPanel. Set the background of each JPanel to light Gray.
But then how can I achieve the dark color lines as shown in the diagram.
Any hints or suggestions?
Try using a JTable and then alternating the colors of the row. This way you can write a generic JComponent (AlternatingColorTable) and use it just like a regular JTable in those 4 panels.
Something like this maybe:
public class AlternatingColorTable extends JTable {
public AlternatingColorTable () {
super();
}
public AlternatingColorTable(TableModel tableModel) {
super(tableModel);
}
/** Extends the renderer to alternate row colors */
public Component prepareRenderer(TableCellRenderer renderer, int row, int col) {
Component returnComp = super.prepareRenderer(renderer, row, col);
Color alternateColor = Color.GRAY;
Color mainColor = Color.DARK_GRAY;
if (!returnComp.getBackground().equals(getSelectionBackground())) {
Color background = (row % 2 == 0 ? alternateColor : mainColor );
returnComp.setBackground(background);
background = null;
}
return returnComp;
}
}
Just make each of the colored bars themselves panels with a different background color. Don't forget to make the panels explicitly opaque with setOpaque(true) - panels are transparent by default transparent in most look and feels.
A note on aesthetics; I would start with the first line in each group shaded differently.

Categories