Highlights subString in the TableCell(s) which is using for JTable filetering
how is possible to synchonize text typed into JTextField
(then by DocumentListener
passed String
to the RowFilter
) with TableCell
if contains same as String value in JTextField/Document,
and then highlights (meaning f.e. change text Color.Red
) for identical text in synchronized TableCell(s)
I know that by usage of some hacks is that possible by using
1/ by using getTableCellRendererComponent
2/ by using prepareRenderer
is there another and maybe correct way(s)
little bit modified code from JTable tutorial
import java.awt.*;
import java.util.regex.PatternSyntaxException;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class TableFilterSorter extends JPanel {
private boolean DEBUG = false;
private static final long serialVersionUID = 1L;
public TableFilterSorter() {
super(new BorderLayout(5, 5));
final JTextField filterCpText = new JTextField();
filterCpText.setFont(new Font("Serif", Font.BOLD, 28));
filterCpText.setForeground(Color.BLUE);
filterCpText.setBackground(Color.LIGHT_GRAY);
JPanel filterCpPanel = new JPanel();
filterCpPanel.setLayout(new BorderLayout(5, 5));
filterCpPanel.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
filterCpPanel.setBackground(Color.LIGHT_GRAY);
filterCpPanel.setPreferredSize(new Dimension(300, 30));
filterCpPanel.add(filterCpText, BorderLayout.CENTER);
add(filterCpPanel, BorderLayout.NORTH);
final JTable table = new JTable(new MyTableModel());
table.setPreferredScrollableViewportSize(new Dimension(500, 160));
table.setFillsViewportHeight(true);
JScrollPane scrollPane = new JScrollPane(table);
add(scrollPane, BorderLayout.CENTER);
TableModel myTableModel = table.getModel();
final TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(myTableModel);
table.setRowSorter(sorter);
filterCpText.getDocument().addDocumentListener(new DocumentListener() {
private void searchFieldChangedUpdate(DocumentEvent evt) {
String text = filterCpText.getText();
if (text.length() == 0) {
sorter.setRowFilter(null);
table.clearSelection();
} else {
try {
sorter.setRowFilter(RowFilter.regexFilter("(?i)" + text, 4));
table.clearSelection();
} catch (PatternSyntaxException pse) {
JOptionPane.showMessageDialog(null, "Bad regex pattern",
"Bad regex pattern", JOptionPane.ERROR_MESSAGE);
}
}
}
@Override
public void insertUpdate(DocumentEvent evt) {
searchFieldChangedUpdate(evt);
}
@Override
public void removeUpdate(DocumentEvent evt) {
searchFieldChangedUpdate(evt);
}
@Override
public void changedUpdate(DocumentEvent evt) {
searchFieldChangedUpdate(evt);
}
});
}
private class MyTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
private String[] columnNames = {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian"};
private Object[][] data = {
{"Mary", "Campione", "Snowboarding", new Integer(5), false},
{"Alison", "Huml", "Rowing", new Integer(3), true},
{"Kathy", "Walrath", "Knitting", new Integer(2), false},
{"Sharon", "Zakhour", "Speed reading", new Integer(20), true},
{"Philip", "Milne", "Pool", new Integer(10), false},
{"Mary", "Campione", "Snowboarding", new Integer(5), false},
{"Alison", "Huml", "Rowing", new Integer(3), true},
{"Kathy", "Walrath", "Knitting", new Integer(2), false},
{"Sharon", "Zakhour", "Speed reading", new Integer(20), true},
{"Philip", "Milne", "Pool", new Integer(10), false},};
@Override
public int getColumnCount() {
return columnNames.length;
}
@Override
public int getRowCount() {
return data.length;
}
@Override
public String getColumnName(int col) {
return columnNames[col];
}
@Override
public Object getValueAt(int row, int col) {
return data[row][col];
}
@Override
public Class<?> getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
@Override
public boolean isCellEditable(int row, int col) {
if (col < 2) {
return false;
} else {
return true;
}
}
@Override
public void setValueAt(Object value, int row, int col) {
if (DEBUG) {
System.out.println("Setting value at " + row + "," + col
+ " to " + value + " (an instance of " + value.getClass() + ")");
}
data[row][col] = value;
fireTableCellUpdated(row, col);
if (DEBUG) {
System.out.println("New value of data:");
printDebugData();
}
}
private void printDebugData() {
int numRows = getRowCount();
int numCols = getColumnCount();
for (int i = 0; i < numRows; i++) {
System.out.print(" row " + i + ":");
for (int j = 0; j < numCols; j++) {
System.out.print(" " + data[i][j]);
}
System.out.println();
}
System.out.println("--------------------------");
}
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("TableDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TableFilterSorter newContentPane = new TableFilterSorter();
newContentPane.setOpaque(true);
frame.setContentPane(newContentPane);
frame.setLocation(150, 150);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
createAndShowGUI();
}
});
}
}
Solution 1:
JXTable can do so via a Highlighter - see swinglabs-demos for an example (MatchingTextHighlighter in the search demo) - there a background highlight is applied by a Painter.
You can do something like that manually somewhere in your renderer. If using a JLabel as renderingComponent, you basically have to find parts of the text that need the background highlight and paint the background for that region (below is a code snippet for finding the areas, c&p from the demos example). Alternatively you might consider using a JTextField as rendering component: the adv is that Highlighter (from javax.swing.text) is built-in, the disadv are the usual issues with text comps as renderers ;-)
/**
* Finds the rectangles that contain rendered characters that match the
* pattern.
*
* @param object an optional configuration parameter. This may be null.
* @param width width of the area to paint.
* @param height height of the area to paint.
* @return a <code>List</code> of <code>Rectangle</code>s marking characters
* to highlight
*/
protected List<Rectangle> findHighlightAreas(JLabel object, int width,
int height) {
String text = object.getText();
insets = object.getInsets(insets);
viewR.x = 0 + insets.left;
viewR.y = 0 + insets.bottom;
viewR.width = width - insets.right;
viewR.height = height - insets.top;
// Reset the text and view rectangle x any y coordinates.
// These are not set to 0 in SwingUtilities.layoutCompoundLabel
iconR.x = iconR.y = 0;
textR.x = textR.y = 0;
FontMetrics fm = object.getFontMetrics(object.getFont());
String clippedText = SwingUtilities.layoutCompoundLabel(object, fm,
text, object.getIcon(), object.getVerticalAlignment(), object
.getHorizontalAlignment(), object
.getVerticalTextPosition(), object
.getHorizontalTextPosition(), viewR, iconR, textR,
object.getIconTextGap());
int xOffset = calculateXOffset(object, viewR, textR);
String textToSearch = clippedText;
// Check to see if the text will be clipped
if (!object.getText().equals(clippedText)) {
// TODO There has to be a better way that assuming ellipses
// are the last characters of the text
textToSearch = clippedText.substring(0, clippedText.length() - 3);
}
return createHighlightAreas(textToSearch, fm, xOffset, height);
}
/**
* Creates the rectangles that contain matched characters in the given text.
*
* @param text the text to search
* @param fm the font metrics of the rendered font
* @param xOffset the x offset at which text rendering starts
* @param height the height of painted highlights
* @return a <code>List</code> of highlight areas to paint
*/
protected List<Rectangle> createHighlightAreas(String text, FontMetrics fm,
int xOffset, int height) {
SearchPredicate predicate = (SearchPredicate) getHighlightPredicate();
Matcher matcher = predicate.getPattern().matcher(text);
List<Rectangle> highlightAreas = null;
int startFrom = 0;
while (startFrom < text.length() && matcher.find(startFrom)) {
if (highlightAreas == null) {
highlightAreas = new ArrayList<Rectangle>();
}
int start = matcher.start();
int end = matcher.end();
if (start == end) {
// empty matcher will cause infinite loop
break;
}
startFrom = end;
int highlightx;
int highlightWidth;
if (start == 0) {
// start highlight from the start of the field
highlightx = textR.x + xOffset;
} else {
// Calculate the width of the unhighlighted text to
// get the start of the highlighted region.
String strToStart = text.substring(0, start);
highlightx = textR.x + fm.stringWidth(strToStart) + xOffset;
}
// Get the width of the highlighted region
String highlightText = text.substring(start, end);
highlightWidth = fm.stringWidth(highlightText);
highlightAreas.add(new Rectangle(highlightx, 0, highlightWidth,
height));
}
if (highlightAreas == null) {
highlightAreas = Collections.emptyList();
} else {
coalesceHighlightAreas(highlightAreas);
}
return highlightAreas;
}
/**
* Joins highlight rectangles that mark adjacent horizontal areas into
* single rectangles. This is useful to renderers that vary horizontally,
* such a horizontal gradient - the gradient will not restart when there are
* two adjacent highlight areas.
*
* @param highlightAreas a <code>List</code> of <code>Rectangle</code>s.
*/
protected void coalesceHighlightAreas(List<Rectangle> highlightAreas) {
Collections.sort(highlightAreas, X_AXIS_RECTANGLE_COMPARATOR);
int i = 0;
while (i < highlightAreas.size() - 1) {
Rectangle r1 = highlightAreas.get(i);
Rectangle r2 = highlightAreas.get(i + 1);
if (r1.x + r1.width == r2.x) {
r1.width += r2.width;
highlightAreas.remove(i + 1);
} else {
i++;
}
}
}
/**
* Calculates the x offset of highlights based on component orientation and
* text direction.
*
* @param component the renderer component
* @param viewR the view rectangle of the renderer component
* @param textR the text rectangle of the renderer component
* @return the number of pixels to offset the highlight from the left edge
* of the component
*/
protected int calculateXOffset(JLabel component, Rectangle viewR,
Rectangle textR) {
int horizAlignment = component.getHorizontalAlignment();
boolean leftToRight = component.getComponentOrientation()
.isLeftToRight();
if (horizAlignment == SwingConstants.LEFT
|| (horizAlignment == SwingConstants.LEADING && leftToRight)
|| (horizAlignment == SwingConstants.TRAILING && !leftToRight)) {
return 0;
} else if (horizAlignment == SwingConstants.RIGHT
|| (horizAlignment == SwingConstants.TRAILING && !leftToRight)
|| (horizAlignment == SwingConstants.LEADING && leftToRight)) {
return viewR.width - textR.width;
} else if (horizAlignment == SwingConstants.CENTER) {
return (viewR.width - textR.width) / 2;
}
throw new AssertionError("Unknown horizonal alignment "
+ horizAlignment);
}
Solution 2:
inspired by kleopatra' reply, I tried everything what's possible or know, now result is that her example invoking me Pattern
looks like Pattern is quickiest, more than I expected, this code missed something about RowSorter, it going about idea how to doing it, with quick output to the GUI,
import java.awt.*;
import java.util.Vector;
import java.util.regex.Pattern;
import javax.swing.*;
import javax.swing.table.*;
public class HiglightNumberValueInTableCell {
private String testS;
private JFrame frame = new JFrame("frameTitle");
private JScrollPane tblS = new JScrollPane();
private JTable tbl;
private Vector<String> rOrH;
private long t1 = 0L;
private long t2 = 0L;
public HiglightNumberValueInTableCell() {
t1 = System.currentTimeMillis();
int regLenght = 25000;
int chars = 0;
AlphaChars aChars = new AlphaChars();
testS = aChars.getNext(regLenght);
rOrH = new Vector<String>();
Vector<Vector<String>> rowD = new Vector<Vector<String>>();
for (int e = 0; e < regLenght;) {
chars++;
//if (chars > 50) { //one char in table cell
if (chars > 20) {
chars = 1;
rowD.add(rOrH);
rOrH = new Vector<String>();
}
//String str = (testS.substring(e, (e + 1))).toString();//one char in table cell
String str = (testS.substring(e, (e + 5))).toString();
if (str != null) {
rOrH.add(str);
} else {
rOrH.add("");
}
//e++;//one char in table cell
e += 5;
}
rOrH = new Vector<String>();
//for (int i = 0; i < 50; i++) {//one char in table cell
for (int i = 0; i < 20; i++) {
rOrH.add(String.valueOf(i + 1));
}
tbl = new JTable(rowD, rOrH);
tblS = new JScrollPane(tbl, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
tblS.setPreferredSize(new Dimension(1000, 403));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(tblS, BorderLayout.CENTER);
frame.setLocation(50, 50);
frame.pack();
addColumnRenderes();
}
private void addColumnRenderes() {
for (int i = 0; i < tbl.getColumnCount(); i++) {
RowColorRenderer rowRenderer = new RowColorRenderer(i);
TableColumn column = tbl.getColumnModel().getColumn(i);
column.setCellRenderer(rowRenderer);
}
Runnable doRun = new Runnable() {
@Override
public void run() {
showFrame();
}
};
SwingUtilities.invokeLater(doRun);
}
private void showFrame() {
Runnable doRun = new Runnable() {
@Override
public void run() {
frame.setVisible(true);
t2 = System.currentTimeMillis();
System.out.println("miliSec:" + (t2 - t1)); //aver. 45 miliSec.
}
};
SwingUtilities.invokeLater(doRun);
}
private class RowColorRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
private int colNo = 0;
RowColorRenderer(int col) {
colNo = col;
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
Component comp = super.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
JComponent jc = (JComponent) comp;
if (!isSelected) {
if (table.getValueAt(row, colNo) != null) {
String str = table.getValueAt(row, colNo).toString();
if (!str.isEmpty()) {
if (Pattern.compile("\\d").matcher(str).find()) {
if (((Pattern.compile("[02468]").matcher(str).find()))
&& (!(Pattern.compile("[13579]").matcher(str).find()))) {
setForeground(Color.magenta);
setBackground(Color.orange);
} else if ((!(Pattern.compile("[02468]").matcher(str).find()))
&& ((Pattern.compile("[13579]").matcher(str).find()))) {
setForeground(Color.blue);
setBackground(Color.yellow);
} else if (((Pattern.compile("[02468]").matcher(str).find()))
&& ((Pattern.compile("[13579]").matcher(str).find()))) {
setForeground(Color.red);
setBackground(Color.cyan);
}
setFont(new Font("Serif", Font.BOLD, 12));
setHorizontalAlignment(CENTER);
} else {
setBackground(table.getBackground());
setForeground(table.getForeground());
setFont(new Font("Serif", Font.PLAIN, 8));
setHorizontalAlignment(CENTER);
}
}
}
}
return this;
}
}
private class AlphaChars {
public static final int MIN_LENGTH = 2000;
private java.util.Random rand = new java.util.Random();
private char[] AlphaChars = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '+', '-', '*', '/', '<', '>', '&',
'#', '@', '{', '}', '?', ':', '_', '"', '!', ')', '('};
public String getNext() {
StringBuilder strbuf = new StringBuilder();
for (int i = 0; i < MIN_LENGTH; i++) {
strbuf.append(getAlphaChars()[getRand().nextInt(getAlphaChars().length)]);
}
return strbuf.toString();
}
public String getNext(int reqLenght) {
StringBuilder strbuf = new StringBuilder();
for (int i = 0; i < reqLenght; i++) {
strbuf.append(getAlphaChars()[getRand().nextInt(getAlphaChars().length)]);
}
return strbuf.toString();
}
public java.util.Random getRand() {
return rand;
}
public void setRand(java.util.Random aRand) {
rand = aRand;
}
public char[] getAlphaChars() {
return AlphaChars;
}
public void setAlphaChars(char[] aAlphaChars) {
AlphaChars = aAlphaChars;
}
}
public static void main(String args[]) {
HiglightNumberValueInTableCell hnvit = new HiglightNumberValueInTableCell();
}
}