views:

101

answers:

2

Before any one suggests HTML, I explain later why thats not an option here. I have a table that contains a column with text cells in it. I need to be able to highlight some of the text in each cell. So for example if the cell contained "cat foo dog"... I might want to highlight foo.

My current method is to use a custom table cell render that puts html into a JLabel component that gets rendered and for a time it was good. Then I notice that when the text in the cell became too long to fit in the column width it just truncated the text without the normal "..." that happens normally in this case. Thus users didnt know there was more text they were not seeing. The other problem was that if the original text itself contained HTML, which in my case it does at times, the cell would not render correctly. I know I could just escape the html but I would still have the prevous problem.

If I a component other than a jlabel then it makes my table's cells look strange. Does any one have any suggestions. Thanks

+2  A: 

Well, here is a solution.

In short, you can subclass JLabel to draw the highlight manually. Override the paintComponent method to do the actual drawing and use FontMetrics to calculate where the highlighted region should be drawn.

Here is that answer in excruciating detail:

Basically, you can make a subclass of JLabel that can highlight stuff. I would do that like this; you may want to do it somewhat differently:

Add a method that tells the label which part to highlight. This could be something like this, assuming you just need one highlighted region:

public void highlightRegion(int start, int end) {
     // Set some field to tell you where the highlight starts and ends...
}

If you need multiple regions, just have an ArrayList instead of a simple field. A method for dehighlighting would probably be useful too.

Now, you need to override the paintComponent method of JLabel. Here you need to do several discrete steps, which you may want to organize in different methods or something. For simplicity, I'll put it all in the paint method.

@Override
protected void paintComponent(Graphics g) {
  ...

First, you need to figure out the physical dimensions of the highlight, which you can do using the nice FontMetrics class. Create the FontMetrics class for the Font you're using.

  FontMetrics metrics = new FontMetrics(getFont());

Now you can get all the information you need to create a rectangle that will be the highlight. You'll need the starting position, the height and the width. To get this, you'll need two substrings of the JLabel's text as follows:

  String start = getText().substring(0, startOfHighlight);
  String text = getText().substring(startOfHighlight, endOfHighlight);
  //You may also need to account for some offsets here:
  int startX = metrics.stringWidth(start);
  int startY = 0; //You probably have some vertical offset to add here.
  int length = metrics.stringWidth(text);
  int height = metrics.getHeight();

Now you can draw the highlighted region before drawing the rest of the label:

  g.fillRect(startX, startY, length, height);
  super.paintComponent(g);
}

Of course, if you want the highlight to span multiple rows, that will require more work.

If you were wondering, I have actually written something like this before. On a whim, I decided to write my own text area type component from a JPanel, and this was basically the way I handled highlighting. Reinventing the wheel may be stupid in an actual project, but it does teach you random stuff that may come in useful...

Tikhon Jelvis
Wow, thanks for your response. That works almost perfectly for JLabels. However I ran into one issue. If the label is setOpaque(true) as is required in my situation it causes the highlight to not show up. This, I think, is caused by calling super.paintComponent(g) after doing the custom drawing. I tried to do the custom drawing of the highlight first but then the highlighted rectangle draws over the text to be highlighted. Its a long story but to get table striping and some other things such as row highlighting to show up in my case I must set the label to opaque. Thanks for the response.
startoftext
Well, try calling `super.paintComponent(g)` first, and use `g.setColor` to set the color to something partially transparent (e.g. `new Color(0x33, 0x66, 0xFF, 0x66)` is a nice blue) before calling `fillRect`.
Tikhon Jelvis
In the end I ended up basically re-implementing my own paint component method to something that worked for my purposes. And gave up on calling the parent paint component method. You answer was a very nice and solution pretty good at answering the title of this post though as relates to a jlabel so I will mark it as accepted. Thank you a bunch Tikhon.
startoftext
A: 

Thats a great answer , and probably the best solution. But an alternative that some might find simpler is to use a JTextfield instead of a JLabel for rendering then you can use JTextfields highlighting capabilities i.e

void highlightWhitespaceText(JTextField text)
    {
        text.setHighlighter(AbstractTableCellRenderer.defaultHighlighter);
        try
        {
            Matcher m = AbstractTableCellRenderer.whitespaceStartPattern.matcher(text.getText());
            if (m.matches())
            {
                text.getHighlighter().addHighlight(m.start(1), m.end(1), AbstractTableCellRenderer.highlightPainter);
            }
            m = AbstractTableCellRenderer.whitespaceEndPattern.matcher(text.getText());
            if (m.matches())
            {
                text.getHighlighter().addHighlight(m.start(1), m.end(1), AbstractTableCellRenderer.highlightPainter);
            }
        }
        catch (BadLocationException ble)
        {
            //
        }
    }

You can chnage the properties of a JTextfield so it looks liek a jLabel in other respects