Wouter Thys
Wouter Thys

Reputation: 99

Filter JTable with search word without losing background color

I have followed the tutorial to filter and highlight text in a JTable here.

The only thing I added is the LookAndFeel, which is set to Nimbus. The code works, except when I select a row, the back and foreground colors of the row are lost.

Without renderer: Selected row has a blue background color

With renderer: Selected row loses back and foreground

In the code, the renderer creates a new label (LabelHighlighted extends JLabel) which overwrites the painComponent method. I guess somehow this method should take the background color of the row in the table.

@Override
protected void paintComponent(Graphics g) {
    if (rectangles.size() > 0) {
        Graphics2D g2d = (Graphics2D) g;
        Color c = g2d.getColor();
        for (Rectangle2D rectangle : rectangles) {
            g2d.setColor(colorHighlight);
            g2d.fill(rectangle);
            g2d.setColor(Color.LIGHT_GRAY);
            g2d.draw(rectangle);
        }
        g2d.setColor(c);
    }
    super.paintComponent(g);
}

Note: I know the JXTable variant of JTable has some more options for filtering and highlighting rows but I did not find a solution yet...

Renderer:

public class RendererHighlighted extends DefaultTableCellRenderer {
     private JTextField searchField;

public RendererHighlighted(JTextField searchField) {
    this.searchField = searchField;
}

@Override
public Component getTableCellRendererComponent(JTable table, Object value,
                                               boolean selected, boolean hasFocus,
                                               int row, int column) {
    Component c = super.getTableCellRendererComponent(table, value, selected, hasFocus, row, column);

    JLabel original = (JLabel) c;
    LabelHighlighted label = new LabelHighlighted();
    label.setFont(original.getFont());
    label.setText(original.getText());
    label.setBackground(original.getForeground());
    label.setForeground(original.getForeground());
    label.setHorizontalTextPosition(original.getHorizontalTextPosition());
    label.highlightText(searchField.getText());
    return label;
}

}

LabelHighlighted:

public class LabelHighlighted extends JLabel {
private List<Rectangle2D> rectangles = new ArrayList<>();
private Color colorHighlight = Color.YELLOW;

public void reset() {
    rectangles.clear();
    repaint();
}

public void highlightText(String textToHighlight) {
    if (textToHighlight == null) {
        return;
    }
    reset();

    final String textToMatch = textToHighlight.toLowerCase().trim();
    if (textToMatch.length() == 0) {
        return;
    }
    textToHighlight = textToHighlight.trim();

    final String labelText = getText().toLowerCase();
    if (labelText.contains(textToMatch)) {
        FontMetrics fm = getFontMetrics(getFont());
        float w = -1;
        final float h = fm.getHeight() - 1;
        int i = 0;
        while (true) {
            i = labelText.indexOf(textToMatch, i);
            if (i == -1) {
                break;
            }
            if (w == -1) {
                String matchingText = getText().substring(i,
                        i + textToHighlight.length());
                w = fm.stringWidth(matchingText);
            }
            String preText = getText().substring(0, i);
            float x = fm.stringWidth(preText);
            rectangles.add(new Rectangle2D.Float(x, 1, w, h));
            i = i + textToMatch.length();
        }
        repaint();
    }
}

@Override
protected void paintComponent(Graphics g) {
    if (rectangles.size() > 0) {
        Graphics2D g2d = (Graphics2D) g;
        Color c = g2d.getColor();
        for (Rectangle2D rectangle : rectangles) {
            g2d.setColor(colorHighlight);
            g2d.fill(rectangle);
            g2d.setColor(Color.LIGHT_GRAY);
            g2d.draw(rectangle);
        }
        g2d.setColor(c);
    }
    super.paintComponent(g);
}

}

Upvotes: 2

Views: 833

Answers (1)

aterai
aterai

Reputation: 9818

It might be easy to use HTML tags for JLabel.

<span style='color:#000000; background-color:#FFFF00'>%s</span>

enter image description here

import java.awt.*;
import java.util.Objects;
import java.util.regex.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

class RendererHighlighted extends DefaultTableCellRenderer {
  private final JTextField searchField;
  public RendererHighlighted(JTextField searchField) {
    this.searchField = searchField;
  }
  @Override public Component getTableCellRendererComponent(
      JTable table, Object value, boolean isSelected, boolean hasFocus,
      int row, int column) {
    String txt = Objects.toString(value, "");
    String pattern = searchField.getText();

    if (Objects.nonNull(pattern) && !pattern.isEmpty()) {
      Matcher matcher = Pattern.compile(pattern).matcher(txt);
      int pos = 0;
      StringBuilder buf = new StringBuilder("<html>");
      while (matcher.find(pos)) {
        int start = matcher.start();
        int end   = matcher.end();
        buf.append(String.format(
            "%s<span style='color:#000000; background-color:#FFFF00'>%s</span>",
            txt.substring(pos, start), txt.substring(start, end)));
        pos = end;
      }
      buf.append(txt.substring(pos));
      txt = buf.toString();
    }
    super.getTableCellRendererComponent(table, txt, isSelected, hasFocus, row, column);
    return this;
  }
}

public class HtmlHighlightCellTest {
  public JComponent makeUI() {
    String[] columnNames = {"A", "B"};
    Object[][] data = {
      {"aaa", "bbaacc"}, {"bbb", "defg"},
      {"ccccbbbbaaabbbbaaeabee", "xxx"}, {"dddaaabbbbb", "ccbbaa"},
      {"cc cc bbbb aaa bbbb e", "xxx"}, {"ddd aaa b bbbb", "cc bbaa"}
    };
    TableModel model = new DefaultTableModel(data, columnNames) {
      @Override public boolean isCellEditable(int row, int column) {
        return false;
      }
      @Override public Class<?> getColumnClass(int column) {
        return String.class;
      }
    };
    JTable table = new JTable(model);
    table.setFillsViewportHeight(true);
    TableRowSorter<? extends TableModel> sorter = new TableRowSorter<>(model);
    table.setRowSorter(sorter);

    JTextField field = new JTextField();
    RendererHighlighted renderer = new RendererHighlighted(field);
    table.setDefaultRenderer(String.class, renderer);
    field.getDocument().addDocumentListener(new DocumentListener() {
      @Override public void insertUpdate(DocumentEvent e) {
        update();
      }
      @Override public void removeUpdate(DocumentEvent e) {
        update();
      }
      @Override public void changedUpdate(DocumentEvent e) {}
      private void update() {
        String pattern = field.getText().trim();
        if (pattern.isEmpty()) {
          sorter.setRowFilter(null);
        } else {
          sorter.setRowFilter(RowFilter.regexFilter("(?i)" + pattern));
        }
      }
    });

    JPanel sp = new JPanel(new BorderLayout(2, 2));
    sp.add(new JLabel("regex pattern:"), BorderLayout.WEST);
    sp.add(field);
    sp.add(Box.createVerticalStrut(2), BorderLayout.SOUTH);
    sp.setBorder(BorderFactory.createTitledBorder("Search"));
    JPanel p = new JPanel(new BorderLayout(2, 2));
    p.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
    p.add(sp, BorderLayout.NORTH);
    p.add(new JScrollPane(table));
    return p;
  }

  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      try {
        UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
      } catch (Exception ex) {
        ex.printStackTrace();
      }
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      f.getContentPane().add(new HtmlHighlightCellTest().makeUI());
      f.pack();
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

Upvotes: 2

Related Questions