Displaying Large JLIST formatted as HTML - java

I have a java applet in which I have to display a large amount of items (dictionary entries). The user needs to be able to select individual items in the list, hence it is implemented as a JList. Generating the list was very quick until I decided to make the display more asthetically pleasing by formatting the individual items using HTML. Now the list looks pretty but it takes between 10 and 15 seconds to generate it each time the user accesses the dictionary (without formatting it occurs almost instantly). I suppose I could take the performance hit up front by generating the list when the user first enters the application and just hiding and unhiding the list as needed. But, I'm wondering if there is a better way. Perhaps a more efficient way to generate the list.
Here is the section of code where the slow down occurrs (Between the display of C and D):
DefaultListModel dictCodeModel = new DefaultListModel();
System.out.println("C");
while (e.hasMoreElements()) {
String currentEntry = "";
DEntry dentry = (DEntry) e.nextElement();
if (!filter)
dictCodeModel.addElement(dentry.theToken); // tokens have embedded html tags
}
System.out.println("D");
As you can see it is pretty simple. When "theToken" is formatted as HTML, I get a real performance hit. Any ideas of what I can do to get around this?
Thanks,

What kind of HTML formatting are you using? If it's just some text styling (font, color), you can use a JLabel, set its properties accordingly and set it as ListCellRenderer for the JList.

The links above are a bit out of date so here's something more up to date.
Simply using JTable is a huge improvement in speed on initial load but a little slow when you first start scrolling. And you have the new problem that the row height needs adjusting. This can be done inside of a custom renderer easy enough by implementing TableCellRenderer since the getTableCellRendererComponent method gives you access to the row, the table and the component. This will however fire a update of the table which will call the same code. If you code appropriately, this won't be a problem. Still, it's better practice to put it somewhere else. I added a listener to the JViewport and only updated the rows that are currently in view. The code I based this on is here
Alternatively, you can use write a ListCellRenderer that returns a JPanel that looks like the HTML. If you only need a JTextArea then you'll need to set its width to ensure it's preferred height is set correctly like in this answer. Again, you have to update the row's width and it makes sense to do this based on the JViewport.
If you're curious about the performance of both approaches, then a custom renderer returning a JPanel is faster than JLabels rendering HTML. Both are reasonably quick though even with lists with a few thousand items. As mentioned, they can be a little slow when you initially scroll.
Finally, here's some code that lets you make a quick comparison yourself:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.Timer;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class JTableHtmlTest extends JFrame {
protected static final long serialVersionUID = 1L;
public static class Item {
public int id;
public String msg;
}
static class TableModel extends AbstractTableModel {
private static final long serialVersionUID = JListTest.serialVersionUID;
private Item[] items = new Item[] {};
public int getRowCount() {
return items.length;
}
public int getColumnCount() {
return 1;
}
public Object getValueAt(int rowIndex, int columnIndex) {
return items[rowIndex];
}
#Override
public String getColumnName(int column) {
return "";
}
public void updateItems() {
SwingWorker<Item[], Void> worker = new SwingWorker<Item[], Void>() {
#Override
protected Item[] doInBackground() throws Exception {
final Item[] tempList = new Item[3000];
for (int i = 0; i < tempList.length; i++) {
Item item = new Item();
item.id = (int) (Math.random() * 10000);
item.msg = "This is the default message that has to be"
+ " long enough to wrap around a few times so that"
+ " we know things are working. It's rather tedious to write.";
tempList[i] = item;
}
return tempList;
}
#Override
protected void done() {
try {
items = get();
fireTableDataChanged();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
};
worker.execute();
}
}
public static class TableRenderer implements TableCellRenderer {
private static final String strColor = "#EDF5F4";
private static final Color strideColor = Color.decode(strColor);
JLabel htmlLabel = new JLabel();
JPanel noHtmlPanel = new JPanel();
JLabel noHtmlLabel = new JLabel();
JTextArea noHTMLTextArea = new JTextArea();
Item toRender = null;
boolean useHtml = false;
public TableRenderer() {
noHTMLTextArea.setWrapStyleWord(false);
noHTMLTextArea.setLineWrap(true);
noHTMLTextArea.setOpaque(false);
Font defaultFont = noHtmlLabel.getFont();
Font boldFont = defaultFont.deriveFont(Font.BOLD);
noHtmlLabel.setFont(boldFont);
noHtmlLabel.setOpaque(false);
noHtmlPanel.setLayout(new BorderLayout());
noHtmlPanel.add(noHtmlLabel, BorderLayout.NORTH);
noHtmlPanel.add(noHTMLTextArea, BorderLayout.SOUTH);
}
public void setUseHtml(boolean useHtml) {
this.useHtml = useHtml;
}
public Component getJlabelRenderer(JTable table, Item value, int row) {
String colorString = "";
if (row % 2 == 0) {
colorString = "background-color:" + strColor + ";";
}
if (toRender != value) {
toRender = value;
htmlLabel.setText("<html><div style='padding:2px;" + "width:"
+ table.getWidth() + ";" + colorString
+ "color:black;'>"
+ "<div style='padding:2px;font-weight:500;'>"
+ "Item " + value.id + "</div>" + value.msg
+ "</div></html>");
}
return htmlLabel;
}
public Component getNoHtmlRenderer(JTable table, Item value, int row) {
if (toRender != value) {
toRender = value;
noHtmlLabel.setText("Item " + value.id);
noHTMLTextArea.setText(value.msg);
if (row % 2 == 0) {
noHtmlPanel.setBackground(strideColor);
noHtmlPanel.setOpaque(true);
} else {
noHtmlPanel.setOpaque(false);
}
}
return noHtmlPanel;
}
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column) {
if (useHtml) {
return getJlabelRenderer(table, (Item) value, row);
} else {
return getNoHtmlRenderer(table, (Item) value, row);
}
}
}
public JTableHtmlTest() {
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel controlPanel = new JPanel();
JButton updaterControl = new JButton("Update 3000");
final JCheckBox useHtmlControl = new JCheckBox("Use HTML");
final TableModel model = new TableModel();
final JTable table = new JTable(model);
final TableRenderer renderer = new TableRenderer();
JScrollPane scrollPane = new JScrollPane(table);
final JLabel durationIndicator = new JLabel("0");
controlPanel.add(useHtmlControl, BorderLayout.WEST);
controlPanel.add(updaterControl, BorderLayout.EAST);
getContentPane().add(controlPanel, BorderLayout.PAGE_START);
getContentPane().add(scrollPane, BorderLayout.CENTER);
getContentPane().add(durationIndicator, BorderLayout.PAGE_END);
table.setDefaultRenderer(Object.class, renderer);
// Only update the JTable row heights when they are in view
final JViewport viewport = scrollPane.getViewport();
viewport.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
Rectangle viewRect = viewport.getViewRect();
int first = table.rowAtPoint(new Point(0, viewRect.y));
if (first == -1) {
return;
}
int last = table.rowAtPoint(new Point(0, viewRect.y
+ viewRect.height - 1));
if (last == -1) {
last = model.getRowCount() - 1;
}
int column = 0;
for (int row = first; row <= last; row++) {
Component comp = table.prepareRenderer(
table.getCellRenderer(row, column),
row, column);
int rowHeight = comp.getPreferredSize().height;
table.setRowHeight(row, rowHeight);
}
}
});
updaterControl.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
renderer.setUseHtml(useHtmlControl.isSelected());
model.updateItems();
}
});
Timer counter = new Timer();
counter.schedule(new TimerTask() {
#Override
public void run() {
String previousCounter = durationIndicator.getText();
final String newCounter = Integer.toString(Integer
.parseInt(previousCounter) + 1);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
durationIndicator.setText(newCounter);
setTitle(newCounter);
}
});
}
}, 0, 100);
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
JTableHtmlTest jlt = new JTableHtmlTest();
jlt.pack();
jlt.setSize(300, 300);
jlt.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}

Related

In Java how would I combine a grid of buttons with a grid of integers to create a minesweeper board?

So far all I have managed to do is combine my two files into one file that creates a GUI with the grid of buttons and runs an instance of minesweeper separately. I need the values from the grid of integers to reflect onto the grid of buttons so that after I click a button it reveals the integer underneath. Any help would be much appreciated.
import javax.swing.*;
import java.awt.event.*;
import java.awt.GridLayout;
import java.awt.Component;
import java.awt.Label;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.JOptionPane;
public class Minesweeper extends JFrame implements MouseListener
{
private JButton [][] gridz;
private int [][] g;
private int max;
public Minesweeper()
{
gridz= new JButton [10][10];
builder();
g=new int[10][10];
setRandom(10);
}
public void setRandom(int max)
{
int z=0;
while(z<max)
{
int r=(int)(Math.random()*g.length);
int c=(int)(Math.random()*g[r].length);
if(g[r][c]==0)
{
g[r][c]=-1;
z++;
}
}
}
public int count(int r, int c)
{
int x=0;
if((r-1)>=0 && (c-1)>=0 && (r-1)<g.length && (c-1)<g.length && g[r-1][c-1]==-1)
{
x++;
}
if((r-1)>=0 && (r-1)<g.length && g[r-1][c]==-1)
{
x++;
}
if((r-1)>=0 && (c+1)>=0 && (r-1)<g.length && (c+1)<g.length && g[r-1][c+1]==-1)
{
x++;
}
if((c-1)>=0 && (c-1)<g.length && g[r][c-1]==-1)
{
x++;
}
if((c+1)>=0 && (c+1)<g.length && g[r][c+1]==-1)
{
x++;
}
if((r+1)>=0 && (c-1)>=0 && (r+1)<g.length && (c-1)<g.length && g[r+1][c-1]==-1)
{
x++;
}
if((r+1)>=0 && (r+1)<g.length && g[r+1][c]==-1)
{
x++;
}
if((r+1)>=0 && (c+1)>=0 && (r+1)<g.length && (c+1)<g.length && g[r+1][c+1]==-1)
{
x++;
}
return x;
}
public void setCounts()
{
for(int r=0; r<g.length; r++)
{
for(int c=0; c<g[r].length; c++)
{
if(g[r][c]==0)
{
g[r][c]=count(r,c);
}
String formatted = String.format("%2d", g[r][c]);
System.out.print(formatted + " ");
}
System.out.println();
}
}
public void builder()
{
setSize(500,500);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new GridLayout(10, 10));
for(int r=0; r<gridz.length; r++)
{
for(int c=0; c<gridz[r].length; c++)
{
gridz[r][c]=new JButton("*");
gridz[r][c].setActionCommand(r+":"+c);
gridz[r][c].addMouseListener(this);
getContentPane().add(gridz[r][c]);
}
}
setVisible(true);
}
public void mousePressed(MouseEvent e)
{
Component c = e.getComponent();
JButton b = (JButton)c;
System.out.println(b.getActionCommand());
String s=b.getActionCommand();
int f=s.indexOf(':');
if(f>=0)
{
String row=s.substring(0, f);
String col=s.substring(f+1);
String text=row+ '!' +col;
b.setText(text);
}
}
public void mouseClicked(MouseEvent e)
{
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public static void main(String[] args)
{
Minesweeper a = new Minesweeper();
a.setCounts();
//JOptionPane.showMessageDialog(null, "BOOM");
}
}
One of the (many) things you want to do, is decouple your code. This means that the "logic" for the "game" should be independent of the "view", meaning that you can easily change the logic without adversely effecting the view.
This is where things like model-view-controller and single responsibility concepts come into play.
What you should (try) and do is start by describing the "data" or "model". What properties does it have and what functionality can be executed by other parties.
Since the models state could change, independently, you'll also need some way to notify interested parties, this is where an observer is helpful.
For example, a model for the game might be represented like...
public interface MineSweeperModel {
public interface Observer {
public void cellValueDidChange(MineSweeperModel model, int col, int row, int value);
}
public int getRows();
public int getColumns();
public int getValueAt(int col, int row);
public void expose(int col, int row);
public void setObserver(Observer observer);
}
Now, that's cool and everything, but what we need is some kind of implementation, which might look something like...
public class DefaultMineSweeperModel implements MineSweeperModel {
private int rows;
private int cols;
private int[][] bombsMap;
private int[][] visibleState;
private Observer observer;
public DefaultMineSweeperModel(int rows, int cols, int maxBombs) {
this.rows = rows;
this.cols = cols;
bombsMap = new int[cols][rows];
visibleState = new int[cols][rows];
Random rnd = new Random();
int count = 0;
while (count < maxBombs) {
int row = rnd.nextInt(rows);
int col = rnd.nextInt(cols);
if (bombsMap[col][row] == 0) {
bombsMap[col][row] = -1;
count++;
}
}
}
#Override
public void setObserver(Observer observer) {
this.observer = observer;
}
#Override
public int getRows() {
return rows;
}
#Override
public int getColumns() {
return cols;
}
#Override
public int getValueAt(int col, int row) {
return visibleState[col][row];
}
#Override
public void expose(int col, int row) {
if (visibleState[col][row] == 0 && bombsMap[col][row] == -1) {
// You've found a mine, might want to do something about it...
visibleState[col][row] = -1;
fireCellValueDidChange(col, row, -1);
} else if (visibleState[col][row] == 0) {
// Empty cell
visibleState[col][row] = 1;
fireCellValueDidChange(col, row, 1);
}
}
protected void fireCellValueDidChange(int col, int row, int value) {
if (observer == null) {
return;
}
observer.cellValueDidChange(this, col, row, value);
}
}
Now, we could just as easily create a "easy", "hard" and "you're going to die" implementations and seed the properties internally, but this basic implementation provides us with enough capacity to configure it.
But why (use a interface)? One of the principles of OO is "information hiding", which is supported by the concept of Polymorphism.
This means we can create any kind of model, with any kind of internal logic, but any one wanting to make use of the model, can easily do so by simply accepting a instance of the interface.
Now, the UI. This is basically responsible for providing a visual representation of the state of the model.
public class MineSweeperPane extends JPanel {
private JButton[] buttons;
private MineSweeperModel model;
public MineSweeperPane(MineSweeperModel model) {
this.model = model;
model.setObserver(new MineSweeperModel.Observer() {
#Override
public void cellValueDidChange(MineSweeperModel model, int col, int row, int value) {
int index = (model.getRows() * row) + col;
if (index >= buttons.length) {
System.err.println("No view for cell # " + col + "x" + row);
return;
}
buttons[index].setText(Integer.toString(value));
}
});
// You should be using ActionListener
MouseListener mouseListener = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
JComponent source = (JComponent) e.getComponent();
// Here's something they won't teach you...
GridBagConstraints gbc = ((GridBagLayout)getLayout()).getConstraints(source);
System.out.println(gbc.gridx + "x" + gbc.gridy);
// Use class based Integer so we don't get NullPointerException
Integer row = (Integer)source.getClientProperty("row");
Integer col = (Integer)source.getClientProperty("col");
if (row == null || col == null) {
System.err.println("!! Invalid cell");
return;
}
System.out.println("Clicked " + col + "x" + row);
getModel().expose(col, row);
}
};
int rowCount = model.getRows();
int colCount = model.getColumns();
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
buttons = new JButton[rowCount * colCount];
for (int row = 0; row < rowCount; row++) {
for (int col = 0; col < colCount; col++) {
JButton btn = makeButton(col, row, model);
btn.addMouseListener(mouseListener);
gbc.gridx = col;
gbc.gridy = row;
add(btn, gbc);
buttons[row * rowCount + col] = btn;
}
}
}
public MineSweeperModel getModel() {
return model;
}
protected JButton makeButton(int col, int row, MineSweeperModel model) {
JButton btn = new JButton(Integer.toString(model.getValueAt(col, row))) {
#Override
public Dimension getPreferredSize() {
return new Dimension(35, 35);
}
};
btn.setFocusable(false);
btn.setFocusPainted(false);
btn.putClientProperty("row", row);
btn.putClientProperty("col", col);
return btn;
}
}
You will note two things.
I use get/setClientProperty to get and set the cell coordinates for the button within the model. This allows me to seed the int values without needing to format/parse them to/from a String, which is, frankly, messy
I also cheated and demonstrated the ability to pull the buttons grid coordinates directly from the GridBagLayout itself. I prefer to the first, but this is a nice side effect not many people know or make use of it.
When "clicked" (and you should be using an ActionListener, but you're apparently not meant to be using an ActionListener because you've not learnt about them ... which begs the question of, why are you using a JButton when ActionListener is the primary mechanism for monitoring for when it's triggered 🙄), the UI updates the model. If there are any changes to the model, the model's observer is notified, which then allows the UI to update its visual state.
I've not put any logic in to the model, apart from exposing the underlying value of the cell (difference between the visialMap and bombMap), so you'll need to do that
There are a number of ways to achieve this. You could simply include a line in your builder method that matches a button to a value from the g array and stores it in a hashmap or some other data structure like so mineHashMap.put(gridz[r][c], g[r][c]);, this way we can check the hashmap later in the mouse event method to find the correct value from the hashmap like so:
int value = mineHashMap.get(b);
b.setText(value+"");`
If starting from scratch, then a quick and easy/dirty solution would be to extend the JButton class to create a customJBotton which would store the integer value inside the class when you create the custom button, and you could contain other logic in it too. Then later when the button is clicked you can easily get the number in the mouse event like so b.setText(b.getMineValue()); or call a custom method you created in the custom button like so b.revealMine(); which would effectively do this.setText(mineValue);
There are many many other ways to do this, but the two options above would slot into your code quite easily. Custom painting on a JPanel rather than using buttons would also be a smart option but it does require more technical understanding and code.

JComboBox too long for JFrame

I have a problem with JComboBoxes and long Strings. The result is that the JComboBox is too long for the JFrame so it doesn't display the whole box.
Here i have a little code which shows my problem:
JFrame frame = new JFrame();
frame.setSize(500, 500);
JPanel panel = new JPanel();
DesignGridLayout layout = new DesignGridLayout(panel);
Vector<ArrayList<String>> content = new Vector<ArrayList<String>>();
ArrayList<String> list = new ArrayList<String>();
list.add("12345");
list.add("abcde");
list.add("12345");
list.add("abcde");
list.add("12345");
list.add("abcde");
list.add("12345");
list.add("abcde");
list.add("12345");
list.add("abcde");
list.add("12345");
list.add("abcde");
content.add(list);
ComboBoxFullMenu<ArrayList<String>> combobox = new ComboBoxFullMenu<ArrayList<String>>(content);
combobox.setRenderer(new DefaultListCellRenderer() {
private static final long serialVersionUID = 1L;
#Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus)
{
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
ArrayList<String> array = (ArrayList<String>) value;
if (!array.isEmpty())
{
String text = "";
for (String info : array)
{
text += info + "; ";
}
setText(text);
}
return this;
}
});
AutoCompleteDecorator.decorate(combobox, new ObjectToStringConverter()
{
#Override
public String getPreferredStringForItem(Object object)
{
if (object != null)
{
if (object instanceof ArrayList)
{
ArrayList<String> list = (ArrayList<String>) object;
String text = "";
for (String info : list)
{
text += info + "; ";
}
return text;
}
else
{
return object.toString();
}
}
return null;
}
});
layout.row().grid().add(combobox);
frame.add(panel);
frame.setVisible(true);
And here is the class ComboBoxFullMenu:
public class ComboBoxFullMenu<E> extends JComboBox<E>
{
/**
*
*/
private static final long serialVersionUID = 1L;
public ComboBoxFullMenu(Vector<E> items)
{
super(items);
addActionListener(this);
}
public ComboBoxFullMenu()
{
super();
addActionListener(this);
}
public ComboBoxFullMenu (DefaultComboBoxModel<E> defaultComboBoxModel)
{
super(defaultComboBoxModel);
addActionListener(this);
}
/**
* Small hack to get pop up menu size bigger enough to show items even though
* the combo box size could be smaller
* */
private boolean layingOut = false;
#Override
public void doLayout()
{
try
{
layingOut = true;
super.doLayout();
}
finally
{
layingOut = false;
}
}
#Override
public Dimension getSize()
{
Dimension dim = super.getSize();
if ( !layingOut )
{
dim.width = Math.max(dim.width, getPreferredSize().width);
}
return dim;
}
}
I also tried using DefaultListCellRenderer and ObjectTroStringConverter to display the selected item in a shorter way, but i need to see all informations in the dropdownmenu and i think the JComboBox calculates its width about the longest item?
I hope you understand my problem otherwise let me know.
Check out Combo Box Popup is allows you to control the width of the popup. You can set the width
to the size of the largest string, or
specify a maximum width.
You can then turn on scrolling in the popup if the string is larger than the specified width.
Try using this WideComboBox. It allows you to set the width of the JComboBox but when the popup is show it shows the entire item. Meaning the popup will be wider than the JComboBox. You also may find this helpful

Adding multiple JProgressBar to TableColumn of JTable

I have added multiple JProgressBar to TableColumn of JTable.
I am updating all the JProgressBar with data after making certain calculations, but only the last ProgressBar(in this case ProgressBar progressObj4) which is added is getting updated.
How can I update all the ProgressBars?
The basic requirement is that I am displaying the status of file in progress bar while uploading . Currently I am hardcoding 4 progress bars to test if all the progress bars are getting update wrt the status of the file, but I need to create them dynamically. The total no of progress bars wrt the no of files which is getting uploaded. Also, how can I fetch the individual instances of the progress bars & update their status ?
I am attaching the source code of the progressbar getting added to the table column.
//tc = object of TableColumn
progressObj1 = new ProgressBarRenderer("Progress1");
progressObj1.setValue(0);
progressObj1.setStringPainted(true);
progressObj1.setBackground(Color.WHITE);
progressObj1.setBorderPainted(true);
tc.setCellRenderer(progressObj1);
progressObj2 = new ProgressBarRenderer("Progress2");
progressObj2.setValue(0);
progressObj2.setStringPainted(true);
progressObj2.setBackground(Color.WHITE);
progressObj2.setBorderPainted(true);
tc.setCellRenderer(progressObj2);
progressObj3 = new ProgressBarRenderer("Progress3");
progressObj3.setValue(0);
progressObj3.setStringPainted(true);
progressObj3.setBackground(Color.WHITE);
progressObj3.setBorderPainted(true);
tc.setCellRenderer(progressObj3);
progressObj4 = new ProgressBarRenderer("Progress4");
progressObj4.setValue(0);
progressObj4.setStringPainted(true);
progressObj4.setBackground(Color.WHITE);
progressObj4.setBorderPainted(true);
tc.setCellRenderer(progressObj4);
basically there are two ways move with JProgressBar by using SwingWorker and Runnable#Thread, example for SwingWorker
import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
public class TableCellProgressBar {
private String[] columnNames = {"String", "ProgressBar"};
private Object[][] data = {{"dummy", 100}};
private DefaultTableModel model = new DefaultTableModel(data, columnNames) {
private static final long serialVersionUID = 1L;
#Override
public Class<?> getColumnClass(int column) {
return getValueAt(0, column).getClass();
}
#Override
public boolean isCellEditable(int row, int col) {
return false;
}
};
private JTable table = new JTable(model);
public JComponent makeUI() {
TableColumn column = table.getColumnModel().getColumn(1);
column.setCellRenderer(new ProgressRenderer());
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
startTask("test");
startTask("error test");
startTask("test");
}
});
JPanel p = new JPanel(new BorderLayout());
p.add(new JScrollPane(table));
return p;
}
//http://java-swing-tips.blogspot.com/2008/03/jprogressbar-in-jtable-cell.html
private void startTask(String str) {
final int key = model.getRowCount();
SwingWorker<Integer, Integer> worker = new SwingWorker<Integer, Integer>() {
private int sleepDummy = new Random().nextInt(100) + 1;
private int lengthOfTask = 120;
#Override
protected Integer doInBackground() {
int current = 0;
while (current < lengthOfTask && !isCancelled()) {
if (!table.isDisplayable()) {
break;
}
if (key == 2 && current > 60) { //Error Test
cancel(true);
publish(-1);
return -1;
}
current++;
try {
Thread.sleep(sleepDummy);
} catch (InterruptedException ie) {
break;
}
publish(100 * current / lengthOfTask);
}
return sleepDummy * lengthOfTask;
}
#Override
protected void process(java.util.List<Integer> c) {
model.setValueAt(c.get(c.size() - 1), key, 1);
}
#Override
protected void done() {
String text;
int i = -1;
if (isCancelled()) {
text = "Cancelled";
} else {
try {
i = get();
text = (i >= 0) ? "Done" : "Disposed";
} catch (Exception ignore) {
ignore.printStackTrace();
text = ignore.getMessage();
}
}
System.out.println(key + ":" + text + "(" + i + "ms)");
}
};
model.addRow(new Object[]{str, 0});
worker.execute();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(new TableCellProgressBar().makeUI());
frame.setSize(320, 240);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class ProgressRenderer extends DefaultTableCellRenderer {
private final JProgressBar b = new JProgressBar(0, 100);
public ProgressRenderer() {
super();
setOpaque(true);
b.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Integer i = (Integer) value;
String text = "Completed";
if (i < 0) {
text = "Error";
} else if (i < 100) {
b.setValue(i);
return b;
}
super.getTableCellRendererComponent(table, text, isSelected, hasFocus, row, column);
return this;
}
}
To my knowledge, a renderer applies to all rows in a given column. It seems to me that you would like to apply several renderers to the same column. Furthermore, it also seems that you are attempting to give the cell renderer state.
I believe it will help to make the renderer stateless and have the renderer's method getTableCellRendererComponent() take care of setting the various (JProgressBar) properties based on values from the current row before the renderer paints the cell.
In other words, you should only need to invoke tc.setCellRenderer() once for any given column, and then have your cell renderer to draw the column for any given row (based on, say, the underlying data model for that table).

How to get the background color of a JList's unselected cell?

I am trying to find the color used by cells that are unselected. This is usually white, however when I call UIManager.getColor("List.background"), it appears to be the same grey color used for JPanels. When I call new JList().getBackground(), I get the same horrid grey color back, but when I actually use the list, it's white. How do I get this white color from JList or UIManager? What I am currently doing to find the background color is this:
String[] contents = {"Foo", "Bar"};
JList list = new JList(contents);
// Prints true
System.out.println(list.getBackground().equals(new Color(237, 236, 235)));
Since List.selectionBackground gives me the blue color I expect to see when I click on a cell, I figured List.background would give me the color of an unselected cell. What is List.background actually returning a value for then?
On related note, is there a listing somewhere of what these keys means? I've found a related question, but none of the answers provide descriptions of the keys.
EDIT: It appears this is the correct way to do this. However, at least in GNOME the problem arises when calling setLookAndFeel.
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
String[] contents = {"Foo", "Bar"};
JList list = new JList(contents);
// Prints true
System.out.println(list.getBackground().equals(new Color(237, 236, 235)));
// Add list to a pane and display it, and it will actually be white
Looks like this might be a bug, sorry guys.
SwingUtilities.updateComponentTreeUI(myFrame);
EDIT:
hmmm, can you clarify me (us) what is your issue based on this example (from died Old.Good.Forum.Sun.com, :-) and I know that not Jeanette resist)
Note: better would be change this value by using getListCellRendererComponent
EDIT2: please for another play with that check camickr UI Defaluts
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class JListDisabledItemDemo implements ItemListener, Runnable {
private static final String ITEMS[] = {"Black", "Blue", "Green", "Orange", "Purple", "Red", "White", "Yellow"};
private JList jList;
private JCheckBox[] checkBoxes;
private boolean[] enabledFlags;
#Override
public void run() {
JPanel pnlEnablers = new JPanel(new GridLayout(0, 1));
pnlEnablers.setBorder(BorderFactory.createTitledBorder("Enabled Items"));
checkBoxes = new JCheckBox[ITEMS.length];
enabledFlags = new boolean[ITEMS.length];
for (int i = 0; i < ITEMS.length; i++) {
checkBoxes[i] = new JCheckBox(ITEMS[i]);
checkBoxes[i].setSelected(true);
checkBoxes[i].addItemListener(this);
enabledFlags[i] = true;
pnlEnablers.add(checkBoxes[i]);
}
jList = new JList(ITEMS);
jList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
jList.setSelectionModel(new DisabledItemSelectionModel());
jList.setCellRenderer(new DisabledItemListCellRenderer());
jList.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
System.out.println("selection");
}
}
});
JScrollPane scroll = new JScrollPane(jList);
scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
JFrame f = new JFrame("Colors");
Container contentPane = f.getContentPane();
contentPane.setLayout(new GridLayout(1, 2));
contentPane.add(pnlEnablers);
contentPane.add(scroll);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(240, 280);
f.setLocationRelativeTo(null);
UIManager.put("List.background", Color.red);
UIManager.put("List.selectionBackground", Color.orange);
UIManager.put("List.selectionForeground", Color.blue);
UIManager.put("Label.disabledForeground", Color.magenta);
SwingUtilities.updateComponentTreeUI(f);
f.setVisible(true);
}
#Override
public void itemStateChanged(ItemEvent event) {
JCheckBox checkBox = (JCheckBox) event.getSource();
int index = -1;
for (int i = 0; i < ITEMS.length; i++) {
if (ITEMS[i].equals(checkBox.getText())) {
index = i;
break;
}
}
if (index != -1) {
enabledFlags[index] = checkBox.isSelected();
jList.repaint();
}
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new JListDisabledItemDemo());
}
private class DisabledItemListCellRenderer extends DefaultListCellRenderer {
private static final long serialVersionUID = 1L;
#Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
if (enabledFlags[index]) {
return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
}
Component comp = super.getListCellRendererComponent(list, value, index, false, false);
comp.setEnabled(false);
return comp;
}
}
private class DisabledItemSelectionModel extends DefaultListSelectionModel {
private static final long serialVersionUID = 1L;
/**
* No need to override addSelectionInterval(int index0, int index1)
* since we're using SINGLE_SELECTION mode for this demo.
*/
#Override
public void setSelectionInterval(int index0, int index1) {
if (enabledFlags[index0]) {
super.setSelectionInterval(index0, index0);
} else {
/*
* The previously selected index is before this one,
* so walk forward to find the next selectable item.
*/
if (getAnchorSelectionIndex() < index0) {
for (int i = index0; i < enabledFlags.length; i++) {
if (enabledFlags[i]) {
super.setSelectionInterval(i, i);
return;
}
}
} /*
* Otherwise, walk backward to find the next selectable item.
*/ else {
for (int i = index0; i >= 0; i--) {
if (enabledFlags[i]) {
super.setSelectionInterval(i, i);
return;
}
}
}
}
}
}
}

After JTable cell setDefaultRenderer to jTextArea how to highlight the selected row in jTextArea

This is my renderer
class tblCalendarRenderer extends JTextArea implements TableCellRenderer {
JTextArea textField;
public tblCalendarRenderer() {
textField = new JTextArea();
}
public Component getTableCellRendererComponent(JTable table,
Object value, boolean selected, boolean focused, int row,
int column) {
textField.setText(value == null ? "" : value.toString());
textField.setLineWrap(true);
textField.setWrapStyleWord(true);
if (column == 0 || column == 6) { // Week-end
textField.setBackground(new Color(255, 220, 220));
} else { // Week
textField.setBackground(new Color(255, 255, 255));
}
if (row % 2 == 0) {
if (value != null) {
if (Integer.parseInt(value.toString()) == realDay
&& currentMonth == realMonth
&& currentYear == realYear) { // Today
textField.setBackground(new Color(220, 220, 255));
}
}
textField.setFont(new java.awt.Font("Dialog",
java.awt.Font.BOLD, 11));
} else {
textField.setFont(new java.awt.Font("Dialog",
java.awt.Font.BOLD, 12));
}
if (selected && row % 2 != 0) {
textField.setBackground(Color.LIGHT_GRAY);
textField.setForeground(Color.black);
}
textField.setBorder(null);
return textField;
}
}
This is the code I tried out to highlight the row in jTextArea. How can i add it into jTable? i tried add textField.addCaretListener(new ExampleCaretListener()); But it will still select whole jTable cell.
class ExampleCaretListener implements CaretListener {
public void caretUpdate(CaretEvent e) {
Color HILIT_COLOR = Color.LIGHT_GRAY;
final Highlighter.HighlightPainter painter;
painter = new DefaultHighlighter.DefaultHighlightPainter(
HILIT_COLOR);
JTextArea textField = (JTextArea) e.getSource();
String lineText = "";
try {
int dot = e.getDot();
int rowStart = Utilities.getRowStart(textField, dot);
int rowEnd = Utilities.getRowEnd(textField, dot);
System.out.println(dot + " " + rowStart + " " + rowEnd);
lineText = textField.getText(rowStart, (rowEnd - rowStart));
textField.getHighlighter().removeAllHighlights();
textField.getHighlighter().addHighlight(rowStart, rowEnd,
painter);
} catch (BadLocationException ex) {
System.out.println(ex);
}
}
}
I guess that the content of each cell is date number so I am not sure what are you trying to do or where do you actually have multiple lines in single cell.
Did you mean on selecting the table cell instead of entire row?
If that is what you meant you could do it by changing the row/column selection models for your table.
If this is not the case please narrow down your problem and provide a complete (cleaned) source code.
Edit:
As eugener said, renderer is simply drawing your JTextArea inside the cell as an image based on value for that cell. You can however create your own custom model to represent the state of the cell (instead of just having a single String value you can use for example MyModel which contains String and some additional data) and render cell based on that model. For example: you can detect mouse clicks (attach mouse listener to Jtable) and then change the state of that model - update selection start and selection end based on mouse position for the specific cell value. Inside getCellRenderer cast value to your object type (MyModel) which contains selection start and selection end data and use it to render text area.
Here is the sample code which increases selection as you click on a cell (you should modify it to set proper caret positions based on clicked mouse position), code is dirty (you should clean it up a little) but works:
insert this in your cell renderer:
CellValue myValue = (CellValue)value;
HighlightPainter painter = new DefaultHighlighter.DefaultHighlightPainter(
Color.green);
textField.getHighlighter().removeAllHighlights();
try {
textField.getHighlighter().addHighlight(myValue.highlightStart, myValue.highlightEnd, painter);
} catch (BadLocationException e) {
System.out.println("Miss");
}
And here is the sample MyModel:
public class MyModel extends AbstractTableModel {
class CellValue {
String value;
int highlightStart;
int highlightEnd;
CellValue(String val) {
this.value = val;
}
#Override
public String toString() {
return value;
}
}
CellValue[][] values = new CellValue[2][7];
public MyModel() {
for(int i = 0; i < 2; i++) {
for(int j=0; j < 7; j++) {
values[i][j] = new CellValue(i + ":" + j);
}
}
}
#Override
public int getColumnCount() {
return 7;
}
#Override
public int getRowCount() {
return 2;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
return values[rowIndex][columnIndex];
}
}
Here is a Main class:
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(500,500));
final JTable table = new JTable(new MyModel());
for(int i =0; i < 7; i++) {
table.getColumnModel().getColumn(i).setCellRenderer(new tblCalendarRenderer());
}
table.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
int row = table.rowAtPoint(e.getPoint());
int column = table.columnAtPoint(e.getPoint());
Object obj = table.getValueAt(row, column);
System.out.println("value " + obj);
CellValue cellValue = (CellValue)obj;
cellValue.highlightEnd++;
table.repaint();
}
});
table.setRowHeight(50);
JScrollPane scp = new JScrollPane(table);
frame.add(scp);
frame.setVisible(true);
}
}
The renderer is simply drawing your JTextArea inside the cell as an image. So highlighting the text is not going to work. The only thing which may work IMO is using JEditorPane for your renderer with text styling to highlight appropriate part of it.
Read more at http://java.sun.com/docs/books/tutorial/uiswing/components/editorpane.html#recap

Categories