How to get ellipsis in a JLabel containing HTML? - java

When I combine HTML tags into the JLabel text I am loosing the ellipsis behavior that is shown when the space is too small to display the complete text. In my specific case, it is a TableCellRenderer which extends JLabel (swing's default or other). Now, when the column width is too small for the text to be shown fully, it is not showing the ellipsis.
See the image below for example:
For the left column I wrapped the text at the renderer with HTML: setText("<html>" + "<strong>" + value.toString() + "</strong>" + "</html>");. As you can see when the column width is too small to contain the text, it is just cut. The right column however, showing the date and time and using DefaultTableCellRenderer is showing ellipsis when it fails to contain the complete text.
So my question is, can I have both? Meaning, wrapping the text with HTML and still get the ellipsis?
UPDATE:
I found the reason for not getting the ellipsis when using HTML. I followed the code from JComponent#paintComponent(Graphics g) all the way down to BasicLabelUI#layoutCL(...). See the following code snippet taken from the last. It is only clipping the string if it does not have the html property (which is true when the label text is wrapped with html). Yet I have no idea how to work around it:
v = (c != null) ? (View) c.getClientProperty("html") : null;
if (v != null) {
textR.width = Math.min(availTextWidth,
(int) v.getPreferredSpan(View.X_AXIS));
textR.height = (int) v.getPreferredSpan(View.Y_AXIS);
} else {
textR.width = SwingUtilities2.stringWidth(c, fm, text);
lsb = SwingUtilities2.getLeftSideBearing(c, fm, text);
if (lsb < 0) {
// If lsb is negative, add it to the width and later
// adjust the x location. This gives more space than is
// actually needed.
// This is done like this for two reasons:
// 1. If we set the width to the actual bounds all
// callers would have to account for negative lsb
// (pref size calculations ONLY look at width of
// textR)
// 2. You can do a drawString at the returned location
// and the text won't be clipped.
textR.width -= lsb;
}
if (textR.width > availTextWidth) {
text = SwingUtilities2.clipString(c, fm, text,
availTextWidth);
textR.width = SwingUtilities2.stringWidth(c, fm, text);
}
textR.height = fm.getHeight();
}

As long as the HTML content is simple, as in your question, the ellipsis showing can be accomplished with a custom made JLabel. Here is a working example.
Just resize the window and you see the ellipsis appear and disappear and the text cut appropriately as the label resizes with the window.
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JLabel;
import javax.swing.border.Border;
public class SimpleHTMLJLabel extends JLabel
{
private static final long serialVersionUID = -1799635451172963826L;
private String textproper;
private String ellipsis = "...";
private int textproperwidth;
private FontMetrics fontMetrics;
private int ellipsisWidth;
private int insetsHorizontal;
private int borderHorizontal;
public SimpleHTMLJLabel(String textstart, String textproper, String textend)
{
super(textstart + textproper + textend);
this.textproper = textproper;
insetsHorizontal = getInsets().left + getInsets().right;
fontMetrics = getFontMetrics(getFont());
calculateWidths();
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e)
{
int availablewidth = getWidth();
if (textproperwidth > availablewidth - (insetsHorizontal + borderHorizontal))
{
String clippedtextproper = textproper;
while (clippedtextproper.length() > 0
&& fontMetrics.stringWidth(clippedtextproper) + ellipsisWidth > availablewidth - (insetsHorizontal + borderHorizontal))
{
clippedtextproper = clipText(clippedtextproper);
}
setText(textstart + clippedtextproper + ellipsis + textend);
} else
{
setText(textstart + textproper + textend);
}
}
});
}
private void calculateWidths()
{
if (textproper != null)
{
textproperwidth = fontMetrics.stringWidth(textproper);
}
if (ellipsis != null)
{
ellipsisWidth = fontMetrics.stringWidth(ellipsis);
}
}
#Override
public void setFont(Font font)
{
super.setFont(font);
fontMetrics = getFontMetrics(getFont());
calculateWidths();
}
private String clipText(String clippedtextproper)
{
return clippedtextproper.substring(0, clippedtextproper.length() - 1);
}
#Override
public void setBorder(Border border)
{
super.setBorder(border);
borderHorizontal = border.getBorderInsets(this).left + border.getBorderInsets(this).right;
}
}
MAIN
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run()
{
JFrame window = new JFrame();
window.setResizable(true);
window.setTitle("Label Test");
window.getContentPane().add(getContent());
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setSize(400, 200);
window.setLocationRelativeTo(null);
window.setVisible(true);
}
});
}
protected static Component getContent()
{
JPanel panel = new JPanel(new BorderLayout());
SimpleHTMLJLabel label = new SimpleHTMLJLabel("<html><strong>", "TEST1test2TEST3test4TEST5test6TEST7test8TEST", "</strong></html>");
label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.BLUE, 5),
BorderFactory.createEmptyBorder(10, 10, 10, 10)));
label.setFont(label.getFont().deriveFont(20F));
panel.add(label, BorderLayout.CENTER);
return panel;
}
}

I'm going to say: No, you can't have both.
I think if you want custom styling and ellipsis you will have to do it yourself without HTML and with a custom TableCellRenderer.
If you want to try and have your cake and eat it too, you might be able to get there by creating your own View object and setting it with c.putClientProperty("html", value) but I suspect that the HTML rendering code has no notion of ellipsing (text-overflow is an HTML 5ish feature) so you would have to figure out how to teach it to do that. I suspect this would be very difficult and much harder than just writing your own TableCellRenderer.

Here is a modified version of SimpleHTMLJLabel which is using code above
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.IOException;
import java.io.StringReader;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import javax.swing.JLabel;
import javax.swing.border.Border;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML.Tag;
import javax.swing.text.html.HTMLEditorKit.ParserCallback;
public class SimpleHTMLJLabel extends JLabel {
private static final String ellipsis = "...";
private static final String Set = null;
private int textproperwidth;
private FontMetrics fontMetrics;
private int ellipsisWidth;
private int insetsHorizontal;
private int borderHorizontal;
private List<Entry<String, String>> lines;
static String HTML = "<HTML>";
public SimpleHTMLJLabel() {
insetsHorizontal = getInsets().left + getInsets().right;
fontMetrics = getFontMetrics(getFont());
calculateWidths();
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
int availablewidth = getWidth();
renderHtml(availablewidth);
}
});
}
public SimpleHTMLJLabel(String text) {
this();
setText(text);
}
public SimpleHTMLJLabel(List<Entry<String, String>> lines) {
this();
this.lines = lines;
calculateWidths();
super.setText(HTML + toHtml(lines));
}
#Override
public void setText(String text) {
if (text.toUpperCase().startsWith(HTML)) {
this.lines = parseHtml(text);
calculateWidths();
super.setText(HTML + toHtml(lines));
return;
}
super.setText(text);
}
private List<Entry<String, String>> parseHtml(String text) {
List<Entry<String, String>> ret = new ArrayList<>();
java.util.Map<Tag, MutableAttributeSet> tags = new HashMap<>();
try {
(new javax.swing.text.html.parser.ParserDelegator()).parse(new StringReader(text), new ParserCallback() {
#Override
public void handleEndTag(Tag t, int pos) {
//TODO clean handle MutableAttributeSet a
tags.remove(t);
}
#Override
public void handleStartTag(javax.swing.text.html.HTML.Tag t, MutableAttributeSet a, int pos) {
if (t == Tag.HTML) return;
if (t == Tag.P) return;
if (t == Tag.BR) return;
if (t == Tag.BODY) return;
tags.put(t,a);
}
#Override
public void handleText(char[] data, int pos) {
String formats = tags.entrySet().stream().map(t -> "<" + t.getKey() + getAttrib(t.getValue) + ">").collect(Collectors.joining());
ret.add(new AbstractMap.SimpleEntry<>(formats, new String(data)));
}
private String getAttrib(MutableAttributeSet t) {
// TODO Auto-generated method stub
//return " style='color:red'";
return " " + t;
}
}, false);
} catch (IOException e) {
e.printStackTrace();
}
return ret;
}
private static String toEndTag(String s) {
return s.replace("<", "</");
}
private static String toHtml(List<Entry<String, String>> lines) {
return lines.stream().map(s -> s.getKey() + s.getValue() + toEndTag(s.getKey())).collect(Collectors.joining());
}
private static String toPlain(List<Entry<String, String>> lines) {
return lines.stream().map(s -> s.getValue()).collect(Collectors.joining(" "));
}
static private List<Entry<String, String>> clipText(List<Entry<String, String>> properList) {
Entry<String, String> last = properList.get(properList.size() - 1);
List<Entry<String, String>> ret = properList.subList(0, properList.size() - 1);
String newlastValue = truncate(last.getValue());
if (newlastValue.isEmpty()) {
return ret;
}
List<Entry<String, String>> retNew = new ArrayList<>();
retNew.addAll(ret);
retNew.add(new AbstractMap.SimpleEntry<>(last.getKey(), newlastValue));
return retNew;
}
static private String truncate(String newlastValue) {
newlastValue = newlastValue.substring(0, newlastValue.length() - 1);
while (newlastValue.endsWith(" ")) {
newlastValue = newlastValue.substring(0, newlastValue.length() - 1);
}
return newlastValue;
}
private void calculateWidths() {
if (lines != null) {
textproperwidth = fontMetrics.stringWidth(toPlain(lines));
}
ellipsisWidth = fontMetrics.stringWidth(ellipsis);
}
#Override
public void setFont(Font font) {
super.setFont(font);
fontMetrics = getFontMetrics(getFont());
calculateWidths();
}
#Override
public void setBorder(Border border) {
super.setBorder(border);
borderHorizontal = border.getBorderInsets(this).left + border.getBorderInsets(this).right;
}
}
When using my code in TableCellRenderer you need to resize immediately in constructor time, but when you do not have a size of the column:
public SimpleHTMLJLabel() {
...
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
int availablewidth = getWidth();
renderHtml(availablewidth);
}
});
}
protected void renderHtml(int availablewidth) {
if (lines == null || availablewidth == 0) return;
System.out.println("renderHtml " + textproperwidth + ">" + availablewidth);
if (textproperwidth > availablewidth - (insetsHorizontal + borderHorizontal)) {
List<Entry<String, String>> properList = clipText(lines);
while (properList.size() > 0 && fontMetrics.stringWidth(toPlain(properList)) + ellipsisWidth > availablewidth - (insetsHorizontal + borderHorizontal)) {
properList = clipText(properList);
}
SimpleHTMLJLabel.super.setText(HTML + toHtml(properList) + ellipsis);
} else {
SimpleHTMLJLabel.super.setText(HTML + toHtml(lines));
}
}
#Override
public void reshape(int x, int y, int w, int h) {
if (w > 0) renderHtml(w - 5);
super.reshape(x, y, w, h);
}
and in JTable
table = new JTable(model) {
#Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
TableColumn col = table.getColumnModel().getColumn(column);
javax.swing.table.DefaultTableCellRenderer.UIResource csss;
SimpleHTMLJLabel lab = new SimpleHTMLJLabel(((JLabel)
//lab.setXXX( c.getXXX)); for font bcolor, color, border, etc
lab.setText(c.getText());
lab.renderHtml(col.getWidth() - 5);
return lab;
}
};
One can reuse html-labell component to save GC

Related

Custom JTextArea wrap word style

I've got a JTextArea with setLineWrap(true) and (for now) setWrapStyleWord(true).
The text which is contained in that textArea contains almost no white space, and so the wrapping never occurs. But the text is semi-colon separated. And so I'd like to achieve a wrap style at the ";" instead of at the " ".
With the following text:
hello;world;foo;bar;I am the Wizard;of;Oz
Wrapping like
hello;world;foo;bar;
I am the Wizard;
of;Oz
Instead of (with setWrapStyleWord(true))
hello;world;foo;bar;I
am the Wizard;of;Oz
or
Instead of (with setWrapStyleWord(false))
hello;world;foo;bar;I a
m the Wizard;of;Oz
Any idea on how to realize this ?
The solution is to tweak the ComponentUI used by the JTextArea. See this discussion.
One could create directly a new ComponentUI and assign it to the JTextArea, but I recommend to create a new JTextArea subclass handling all this under the radar.
The Component
package com.zparkingb.swing;
import com.zparkingb.swing.ui.SeparatedTextAreaUI;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.text.Document;
public class ZSeparatedTextArea extends JTextArea {
private final Character wordSeparator;
private static final String uiClassID = "SeparatedTextAreaUI";
public ZSeparatedTextArea(Character separator) {
super();
this.wordSeparator = separator;
}
public ZSeparatedTextArea(Character separator, String text) {
super(text);
this.wordSeparator = separator;
}
public ZSeparatedTextArea(Character separator, int rows, int columns) {
super(rows, columns);
this.wordSeparator = separator;
}
public ZSeparatedTextArea(Character separator, String text, int rows, int columns) {
super(text, rows, columns);
this.wordSeparator = separator;
}
public ZSeparatedTextArea(Character separator, Document doc) {
super(doc);
this.wordSeparator = separator;
}
public ZSeparatedTextArea(Character separator, Document doc, String text, int rows, int columns) {
super(doc, text, rows, columns);
this.wordSeparator = separator;
}
public Character getWordSeparator() {
return wordSeparator;
}
public void setUI(SeparatedTextAreaUI ui) {
super.setUI(ui);
}
#Override
public void updateUI() {
if (UIManager.get(getUIClassID()) != null) {
SeparatedTextAreaUI ui = (SeparatedTextAreaUI) UIManager.getUI(this);
setUI(ui);
}
else {
setUI(new SeparatedTextAreaUI());
}
}
public SeparatedTextAreaUI getUI() {
return (SeparatedTextAreaUI) ui;
}
#Override
public String getUIClassID() {
return uiClassID;
}
}
The ComponentUI
package com.zparkingb.swing.ui;
import com.zparkingb.swing.ZSeparatedTextArea;
import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTextAreaUI;
import javax.swing.text.Element;
import javax.swing.text.View;
public class SeparatedTextAreaUI extends BasicTextAreaUI {
private ZSeparatedTextArea textArea = null;
/**
* Creates the view for an element. Returns a SeparatedWrappedPlainView (or WrappedPlainView/PlainView if no wordSeparator is provided)
*
* #param elem the element
*
* #return the view
*/
public View create(Element elem) {
if (textArea.getWordSeparator() == null)
return super.create(elem);
View v = new SeparatedWrappedPlainView(textArea.getWordSeparator(), elem);
return v;
}
public static ComponentUI createUI(JComponent c) {
return new SeparatedTextAreaUI();
}
#Override
public void installUI(JComponent c) {
textArea = (ZSeparatedTextArea) c;
super.installUI(c);
}
#Override
public void uninstallUI(JComponent c) {
super.uninstallUI(c);
textArea = null;
}
}
The View
This one is more tricky. As some information required to do the word wrapping are private in SeparatedWrappedPlainView they have been duplicated (e.g. metrics)
package com.zparkingb.swing.ui;
import java.awt.Container;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import java.text.BreakIterator;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.Segment;
import javax.swing.text.TabExpander;
import javax.swing.text.Utilities;
import javax.swing.text.WrappedPlainView;
class SeparatedWrappedPlainView extends WrappedPlainView {
private int _tabBase;
private FontMetrics _metrics;
private final Character wordSeparator;
public SeparatedWrappedPlainView(Character wordSeparator, Element elem) {
super(elem);
assert(wordSeparator!=null);
this.wordSeparator=wordSeparator;
}
/**
* Rem: Copied from WrappedPlainView only to be able to use our own
* getBreakLocation instead of the {#link Utilities#getBreakLocation} used by default
*/
#Override
protected int calculateBreakPosition(int p0, int p1) {
Segment s = new Segment();
try {
getDocument().getText(p0, p1 - p0, s);
} catch (BadLocationException ex) {
assert false : "Couldn't load text";
}
int width = getWidth();
int pos;
pos = p0 + getBreakLocation(s, _metrics, _tabBase, _tabBase + width, this, p0);
return pos;
}
/**
* Rem: Copied from {#link Utilities#getBreakLocation} in order to
* to break the text on our separator instead of on whitespaces.
*/
private int getBreakLocation(Segment s, FontMetrics metrics, float x0, float x, TabExpander e, int startOffset) {
char[] txt = s.array;
int txtOffset = s.offset;
int txtCount = s.count;
int index = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset, false); //, null, useFPIAPI);
if (index >= txtCount - 1) {
return txtCount;
}
for (int i = txtOffset + index; i >= txtOffset; i--) {
char ch = txt[i];
if (ch < 256) {
// break on separator
if (wordSeparator.equals(ch)) {
index = i - txtOffset + 1;
break;
}
}
else {
// a multibyte char found; use BreakIterator to find line break
BreakIterator bit = BreakIterator.getLineInstance();
bit.setText(s);
int breakPos = bit.preceding(i + 1);
if (breakPos > txtOffset) {
index = breakPos - txtOffset;
}
break;
}
}
return index;
}
void _updateMetrics() {
Container component = getContainer();
_metrics = component.getFontMetrics(component.getFont());
}
#Override
public void paint(Graphics g, Shape a) {
Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
_tabBase = r.x;
_updateMetrics();
super.paint(g, a);
}
#Override
public float getPreferredSpan(int axis) {
_updateMetrics();
return super.getPreferredSpan(axis);
}
#Override
public float getMaximumSpan(int axis) {
_updateMetrics();
return super.getMaximumSpan(axis);
}
#Override
public float getMinimumSpan(int axis) {
_updateMetrics();
return super.getMinimumSpan(axis);
}
#Override
public void setSize(float width, float height) {
_updateMetrics();
super.setSize(width, height);
}
}

Multiple column in JComboBox in java swing

how to create jcomboBox with two /multiple columns in the drop down let but when we select only one selected value show in Jcombobox
please give me any solution for this .
You might be able to use JList#setLayoutOrientation(JList.VERTICAL_WRAP):
import java.awt.*;
import javax.accessibility.Accessible;
import javax.swing.*;
import javax.swing.plaf.basic.ComboPopup;
public class TwoColumnsDropdownTest {
private Component makeUI() {
DefaultComboBoxModel<String> model = new DefaultComboBoxModel<>();
model.addElement("111");
model.addElement("2222");
model.addElement("3");
model.addElement("44444");
model.addElement("55555");
model.addElement("66");
model.addElement("777");
model.addElement("8");
model.addElement("9999");
int rowCount = (model.getSize() + 1) / 2;
JComboBox<String> combo = new JComboBox<String>(model) {
#Override public Dimension getPreferredSize() {
Insets i = getInsets();
Dimension d = super.getPreferredSize();
int w = Math.max(100, d.width);
int h = d.height;
int buttonWidth = 20; // ???
return new Dimension(buttonWidth + w + i.left + i.right, h + i.top + i.bottom);
}
#Override public void updateUI() {
super.updateUI();
setMaximumRowCount(rowCount);
setPrototypeDisplayValue("12345");
Accessible o = getAccessibleContext().getAccessibleChild(0);
if (o instanceof ComboPopup) {
JList<?> list = ((ComboPopup) o).getList();
list.setLayoutOrientation(JList.VERTICAL_WRAP);
list.setVisibleRowCount(rowCount);
list.setFixedCellWidth((getPreferredSize().width - 2) / 2);
}
}
};
JPanel p = new JPanel();
p.add(combo);
return p;
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(new TwoColumnsDropdownTest().makeUI());
frame.setSize(320, 240);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
You need to do a few things.
By default, what is displayed in the JComboBox, as the selected item, is the value returned by method toString of the items in the ComboBoxModel. Based on your comment I wrote an Item class and overrode the toString method.
In order to display something different in the drop-down list, you need a custom ListCellRenderer.
In order for the drop-down list to display the entire details of each item, the drop-down list needs to be wider than the JComboBox. I used code from the following SO question to achieve that:
How can I change the width of a JComboBox dropdown list?
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;
import java.math.BigDecimal;
import java.text.NumberFormat;
import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.swing.BorderFactory;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.plaf.basic.BasicComboPopup;
public class MultiColumnCombo implements PopupMenuListener {
private JComboBox<Item> combo;
public void popupMenuCanceled(PopupMenuEvent event) {
// Do nothing.
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent event) {
// Do nothing.
}
public void popupMenuWillBecomeVisible(PopupMenuEvent event) {
AccessibleContext comboAccessibleContext = combo.getAccessibleContext();
int comboAccessibleChildrenCount = comboAccessibleContext.getAccessibleChildrenCount();
if (comboAccessibleChildrenCount > 0) {
Accessible comboAccessibleChild0 = comboAccessibleContext.getAccessibleChild(0);
if (comboAccessibleChild0 instanceof BasicComboPopup) {
EventQueue.invokeLater(() -> {
BasicComboPopup comboPopup = (BasicComboPopup) comboAccessibleChild0;
JScrollPane scrollPane = (JScrollPane) comboPopup.getComponent(0);
Dimension d = setCurrentDimension(scrollPane.getPreferredSize());
scrollPane.setPreferredSize(d);
scrollPane.setMaximumSize(d);
scrollPane.setMinimumSize(d);
scrollPane.setSize(d);
Point location = combo.getLocationOnScreen();
int height = combo.getPreferredSize().height;
comboPopup.setLocation(location.x, location.y + height - 1);
comboPopup.setLocation(location.x, location.y + height);
});
}
}
}
private void createAndDisplayGui() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createCombo());
frame.setSize(450, 300);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createCombo() {
JPanel panel = new JPanel();
Item[] items = new Item[]{new Item("A", "Item A", new BigDecimal(1.99d), new BigDecimal(2.99d)),
new Item("B", "Item B", new BigDecimal(7.5d), new BigDecimal(9.0d)),
new Item("C", "Item C", new BigDecimal(0.25d), new BigDecimal(3.15d))};
combo = new JComboBox<>(items);
AccessibleContext comboAccessibleContext = combo.getAccessibleContext();
int comboAccessibleChildrenCount = comboAccessibleContext.getAccessibleChildrenCount();
if (comboAccessibleChildrenCount > 0) {
Accessible comboAccessibleChild0 = comboAccessibleContext.getAccessibleChild(0);
if (comboAccessibleChild0 instanceof BasicComboPopup) {
BasicComboPopup comboPopup = (BasicComboPopup) comboAccessibleChild0;
comboPopup.getList().setCellRenderer(new MultiColumnRenderer());
}
}
combo.addPopupMenuListener(this);
panel.add(combo);
return panel;
}
private Dimension setCurrentDimension(Dimension dim) {
Dimension d = new Dimension(dim);
d.width = 120;
return d;
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> new MultiColumnCombo().createAndDisplayGui());
}
}
class Item {
private String code;
private String name;
private BigDecimal price;
private BigDecimal salePrice;
public Item(String code, String name, BigDecimal price, BigDecimal salePrice) {
this.code = code;
this.name = name;
this.price = price;
this.salePrice = salePrice;
}
public String displayString() {
return String.format("%s %s %s %s",
code,
name,
NumberFormat.getCurrencyInstance().format(price),
NumberFormat.getCurrencyInstance().format(salePrice));
}
public String toString() {
return name;
}
}
class MultiColumnRenderer implements ListCellRenderer<Object> {
/** Component returned by method {#link #getListCellRendererComponent}. */
private JLabel cmpt;
public MultiColumnRenderer() {
cmpt = new JLabel();
cmpt.setOpaque(true);
cmpt.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
}
public Component getListCellRendererComponent(JList<? extends Object> list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
String text;
if (value == null) {
text = "";
}
else {
if (value instanceof Item) {
text = ((Item) value).displayString();
}
else {
text = value.toString();
}
}
cmpt.setText(text);
if (isSelected) {
cmpt.setBackground(list.getSelectionBackground());
cmpt.setForeground(list.getSelectionForeground());
}
else {
cmpt.setBackground(list.getBackground());
cmpt.setForeground(list.getForeground());
}
cmpt.setFont(list.getFont());
return cmpt;
}
}

How to highlight keywords in java while opening a file and while the user is typing

So I am trying to highlight the keywords in java,which I have stored in a text file, as the user opens a .java file or writes to .java file. I think I know how to tell if the file is of the right type. However, I do not know how to change the color of certain keywords. If anyone could help out that would be great because right now it's pretty confusing. I was wondering if I could use my replace function somehow. I have tried to go about trying to do this with the few methods I have, yet it's still not clear. I have taken out he majority of the methods and listeners, just know they are there but kept out to make it easier to read.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
import java.util.Scanner;
import java.io.*;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Highlighter;
import javax.swing.text.Highlighter.HighlightPainter;
import javax.swing.text.JTextComponent;
import java.net.URI;
import java.util.*;
public class MyTextEditor extends JFrame implements ActionListener
{
private JPanel panel = new JPanel(new BorderLayout());
private JTextArea textArea = new JTextArea(0,0);
private static final Color TA_BKGRD_CL = Color.BLACK;
private static final Color TA_FRGRD_CL = Color.GREEN;
private static final Color TA_CARET_CL = Color.WHITE;
private JScrollPane scrollPane;
private MenuBar menuBar = new MenuBar();
public MyTextEditor()
{
this.setSize(750,800);
this.setTitle("Zenith");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
textArea.setFont(new Font("Consolas", Font.BOLD, 14));
textArea.setForeground(TA_FRGRD_CL);
textArea.setBackground(TA_BKGRD_CL);
textArea.setCaretColor(TA_CARET_CL);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.setVisible(true);
textArea.add(scrollPane,BorderLayout.EAST);
final LineNumberingTextArea lineNTA = new LineNumberingTextArea(textArea);
DocumentListener documentListen = new DocumentListener()
{
public void insertUpdate(DocumentEvent documentEvent)
{
lineNTA.updateLineNumbers();
}
public void removeUpdate(DocumentEvent documentEvent)
{
lineNTA.updateLineNumbers();
}
public void changedUpdate(DocumentEvent documentEvent)
{
lineNTA.updateLineNumbers();
}
};
textArea.getDocument().addDocumentListener(documentListen);
// Line numbers
lineNTA.setBackground(Color.BLACK);
lineNTA.setForeground(Color.WHITE);
lineNTA.setFont(new Font("Consolas", Font.BOLD, 13));
lineNTA.setEditable(false);
lineNTA.setVisible(true);
textArea.add(lineNTA);
scrollPane.setVisible(true);
scrollPane.add(textArea);
getContentPane().add(scrollPane);
}
public void findKeyWords(String ext)
{
ArrayList<String> wordsInTA = new ArrayList<String>();
int index = 0;
if(ext == "java")
{
for(String line : textArea.getText().split(" "))
{
wordsInTA.add(line);
index++;
}
try
{
while(index>0)
{
String temp = wordsInTA.get(index);
boolean isKeyWord = binarySearch(temp);
if(isKeyWord)
{
//Code that has not yet been made
}
index--;
}
}
catch(IOException ex)
{
ex.printStackTrace();
}
}
}
private ArrayList<String> loadJavaWords() throws FileNotFoundException
{
ArrayList<String> javaWords = new ArrayList<String>();
Scanner scan = new Scanner(new File("JavaKeyWords.txt"));
while(scan.hasNext())
{
javaWords.add(scan.next());
}
scan.close();
return javaWords;
}
private boolean binarySearch(String word) throws FileNotFoundException
{
ArrayList<String> javaWords = loadJavaWords();
int min = 0;
int max = javaWords.size()-1;
while(min <= max)
{
int index = (max + min)/2;
String guess = javaWords.get(index);
int result = word.compareTo(guess);
if(result == 0)
{
return true;
}
else if(result > 0)
{
min = index +1;
}
else if(result < 0)
{
max = index -1;
}
}
return false;
}
public void replace()
{
String wordToSearch = JOptionPane.showInputDialog(null, "Word to replace:");
String wordToReplace = JOptionPane.showInputDialog(null, "Replacement word:");
int m;
int total = 0;
int wordLength = wordToSearch.length();
for (String line : textArea.getText().split("\\n"))
{
m = line.indexOf(wordToSearch);
if(m == -1)
{
total += line.length() + 1;
continue;
}
String newLine = line.replaceAll(wordToSearch, wordToReplace);
textArea.replaceRange(newLine, total, total + line.length());
total += newLine.length() + 1;
}
}
public static void main(String args[])
{
MyTextEditor textEditor = new MyTextEditor();
textEditor.setVisible(true);
}
}

JTextPane highlighting issue

The last days I have been trying to implement a highlighting feature in a small text editor. For some reason I get a strange result:
The given example should highlight each "dolor" - the first occurences are correctly found and highlighted but the next ones aren't.
Here is the code I wrote so far:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.DefaultHighlighter.DefaultHighlightPainter;
import javax.swing.text.DefaultStyledDocument;
/**
* Highlighting created on 04.11.2013<br>
* <br>
* Specification:<br>
*/
public class Highlighting extends JFrame implements MouseListener {
private JScrollPane scrollPane;
private JTextPane textPane;
private DefaultHighlighter highlighter;
private DefaultHighlightPainter painter;
public static void main(String[] args) {
new Highlighting().setVisible(true);
}
/**
*
*/
public Highlighting() {
this.initialize();
this.build();
this.configure();
}
/**
*
*/
public void initialize() {
this.scrollPane = new JScrollPane();
this.textPane = new JTextPane();
this.highlighter = new DefaultHighlighter();
this.painter = new DefaultHighlightPainter(Color.RED);
}
/**
*
*/
public void build() {
this.add(this.scrollPane);
}
/**
*
*/
public void configure() {
this.scrollPane.setViewportView(this.textPane);
this.textPane.setHighlighter(this.highlighter);
this.textPane.addMouseListener(this);
this.textPane.setDocument(new DefaultStyledDocument());
this.setPreferredSize(new Dimension(400, 500));
this.pack();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
/**
*
*/
private void highlight() {
this.highlighter.removeAllHighlights();
String selectedText = this.textPane.getSelectedText();
String text = this.textPane.getText();
int wordlength = selectedText.length();
int index = 0;
while ((index = text.indexOf(selectedText, index)) != -1) {
try {
this.highlighter.addHighlight(index, index + wordlength, this.painter);
} catch (BadLocationException e) {
e.printStackTrace();
}
index += wordlength;
}
}
#Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
this.highlight();
}
}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
}
Does this has something to do with the line separators (\r\n) ?
A JTextComponent's getText() and A JTextPane/JEditorPane's getText() has different implementation. JTextPane/JEditorPane uses EditorKit to write the document content(text) to a StringWriter and then return the text with formatting and inserting a line/paragraph break into the document. But the JTextCompoent returns document content directly by:
document.getText(0, document.getLength());
You will better understand if you try to compare the length : jTextPane1.getText().length() and jTextPane1().getDocument().getLength().
Reproducing the difference in length by inserting string with:
DefaultStyleDocument.insertString(0, str, primaryStyle)
when str = "I\n not" ; document length = 6, getText().length = 7
when str = "I\r\n not" ; document length = 7, getText().length = 8
when str = "I\n\n not" ; document length = 7, getText().length = 9!
So, in your high-lighting text program try reading the content text using:
DefaultStyledDocument document = (DefaultStyledDocument) jTextPane1.getDocument();
try {
contText = document.getText(0, document.getLength());
} catch (BadLocationException ex) {
Logger.getLogger(JTextPaneTest.class.getName()).log(Level.SEVERE, null, ex);
}
Then search for your selected text position in the contText as you were doing and you should be good to go. Because, highlighter.addHighlight(int p0, int p1, Highlighter.HighlightPainter p) uses the document for position offset.
Use CaretListener:
To Highlight upon text selection, It is better to use CaretListener, no need to add mouse and key board selection handling code at all:
jTextPane1.addCaretListener(new CaretListener() {
public void caretUpdate(CaretEvent evt) {
if(evt.getDot() == evt.getMark())return;
JTextPane txtPane = (JTextPane) evt.getSource();
DefaultHighlighter highlighter = (DefaultHighlighter) txtPane.getHighlighter();
highlighter.removeAllHighlights();
DefaultHighlightPainter hPainter = new DefaultHighlightPainter(new Color(0xFFAA00));
String selText = txtPane.getSelectedText();
String contText = "";// = jTextPane1.getText();
DefaultStyledDocument document = (DefaultStyledDocument) txtPane.getDocument();
try {
contText = document.getText(0, document.getLength());
} catch (BadLocationException ex) {
Logger.getLogger(JTextPaneTest.class.getName()).log(Level.SEVERE, null, ex);
}
int index = 0;
while((index = contText.indexOf(selText, index)) > -1){
try {
highlighter.addHighlight(index, selText.length()+index, hPainter);
index = index + selText.length();
} catch (BadLocationException ex) {
Logger.getLogger(JTextPaneTest.class.getName()).log(Level.SEVERE, null, ex);
//System.out.println(index);
}
}
}
});
for example, see
how to remove Highlighter highlighter.removeHighlight(h);
View.modelToView for a new Highlighter
note I don't know how to determine a new line \n inside selection, not possible from this code
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.UIManager;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Document;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import javax.swing.text.LayeredHighlighter;
import javax.swing.text.Position;
import javax.swing.text.View;
public class HighlightExample {
private JFrame f = new JFrame("Highlight example");
private JPanel panel = new JPanel();
private JTextPane textPane = new JTextPane();
private JTextField tf = new JTextField("wrapping!");
private String word;
private Highlighter highlighter = new UnderlineHighlighter(null);
public HighlightExample() {
textPane.setHighlighter(highlighter);
textPane.setText("This text pane contains no html. It supports letter wrapping, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping?, "
+ "\nThis text pane contains no html. It supports letter wrapping-, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping_, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping?, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping-, "
+ "\nThis text pane contains no html. It supports letter wrapping!, "
+ "\nThis text pane contains no html. It supports letter wrapping?");
panel.setLayout(new BorderLayout());
panel.add(new JLabel("Enter word, then press ENTER key: "), "West");
panel.add(tf, "Center");
/*try {
textPane.read(new FileReader("links1.html"), null);
} catch (Exception e) {
System.out.println("Failed to load file " + args[0]);
System.out.println(e);
}*/
final WordSearcher searcher = new WordSearcher(textPane);
tf.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
word = tf.getText().trim();
int offset = searcher.search(word);
if (offset != -1) {
try {
textPane.scrollRectToVisible(textPane
.modelToView(offset));
} catch (BadLocationException e) {
}
}
}
});
textPane.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent evt) {
searcher.search(word);
}
#Override
public void removeUpdate(DocumentEvent evt) {
searcher.search(word);
}
#Override
public void changedUpdate(DocumentEvent evt) {
}
});
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(panel, "South");
f.add(new JScrollPane(textPane), "Center");
f.setSize(400, 400);
f.setVisible(true);
}
public static void main(String[] args) {
UIManager.put("TextPane.caretForeground", Color.yellow);
UIManager.put("TextPane.selectionBackground", Color.green);
UIManager.put("TextPane.selectionForeground", Color.blue);
/*try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (Exception evt) {
}*/
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new HighlightExample();
}
});
}
}
// A simple class that searches for a word in
// a document and highlights occurrences of that word
class WordSearcher {
protected JTextComponent comp;
protected Highlighter.HighlightPainter painter;
public WordSearcher(JTextComponent comp) {
this.comp = comp;
this.painter = new UnderlineHighlighter.UnderlineHighlightPainter(Color.red);
}
// Search for a word and return the offset of the first occurrence.
// Highlights are added for all occurrences found.
public int search(String word) {
int firstOffset = -1;
Highlighter highlighter = comp.getHighlighter();
// Remove any existing highlights for last word
Highlighter.Highlight[] highlights = highlighter.getHighlights();
for (int i = 0; i < highlights.length; i++) {
Highlighter.Highlight h = highlights[i];
if (h.getPainter() instanceof UnderlineHighlighter.UnderlineHighlightPainter) {
highlighter.removeHighlight(h);
}
}
if (word == null || word.equals("")) {
return -1;
}
String content = null; // Look for the word we are given - insensitive search
try {
Document d = comp.getDocument();
content = d.getText(0, d.getLength()).toLowerCase();
} catch (BadLocationException e) {
return -1; // Cannot happen
}
word = word.toLowerCase();
int lastIndex = 0;
int wordSize = word.length();
while ((lastIndex = content.indexOf(word, lastIndex)) != -1) {
int endIndex = lastIndex + wordSize;
try {
highlighter.addHighlight(lastIndex, endIndex, painter);
} catch (BadLocationException e) {
// Nothing to do
}
if (firstOffset == -1) {
firstOffset = lastIndex;
}
lastIndex = endIndex;
}
return firstOffset;
}
}
class UnderlineHighlighter extends DefaultHighlighter {
protected static final Highlighter.HighlightPainter sharedPainter = new UnderlineHighlightPainter(null);// Shared painter used for default highlighting
protected Highlighter.HighlightPainter painter; // Painter used for this highlighter
public UnderlineHighlighter(Color c) {
painter = (c == null ? sharedPainter : new UnderlineHighlightPainter(c));
}
// Convenience method to add a highlight with the default painter.
public Object addHighlight(int p0, int p1) throws BadLocationException {
return addHighlight(p0, p1, painter);
}
#Override
public void setDrawsLayeredHighlights(boolean newValue) {
if (newValue == false) {// Illegal if false - we only support layered highlights
throw new IllegalArgumentException(
"UnderlineHighlighter only draws layered highlights");
}
super.setDrawsLayeredHighlights(true);
}
// Painter for underlined highlights
public static class UnderlineHighlightPainter extends LayeredHighlighter.LayerPainter {
protected Color color; // The color for the underline
public UnderlineHighlightPainter(Color c) {
color = c;
}
#Override
public void paint(Graphics g, int offs0, int offs1, Shape bounds,
JTextComponent c) {// Do nothing: this method will never be called
}
#Override
public Shape paintLayer(Graphics g, int offs0, int offs1, Shape bounds,
JTextComponent c, View view) {
g.setColor(color == null ? c.getSelectionColor() : color);
Rectangle alloc = null;
if (offs0 == view.getStartOffset() && offs1 == view.getEndOffset()) {
if (bounds instanceof Rectangle) {
alloc = (Rectangle) bounds;
} else {
alloc = bounds.getBounds();
}
} else {
try {
Shape shape = view.modelToView(offs0, Position.Bias.Forward, offs1,
Position.Bias.Backward, bounds);
alloc = (shape instanceof Rectangle) ? (Rectangle) shape : shape.getBounds();
} catch (BadLocationException e) {
return null;
}
}
FontMetrics fm = c.getFontMetrics(c.getFont());
int baseline = alloc.y + alloc.height - fm.getDescent() + 1;
g.drawLine(alloc.x, baseline, alloc.x + alloc.width, baseline);
g.drawLine(alloc.x, baseline + 1, alloc.x + alloc.width, baseline + 1);
return alloc;
}
}
}

Java JTextField with input hint

I would like to add a hint value to my javax.swing.JTextField. It should look like Firefox rendering of <input type="text" title="bla">. This creates an edit field with the text 'bla' in the background. If the textbox has focus the title-text disappears and just reappears if the user leaves the editbox without text.
Is there a (free) swing component that does something like this?
You could create your own:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
final JFrame frame = new JFrame();
frame.setLayout(new BorderLayout());
final JTextField textFieldA = new HintTextField("A hint here");
final JTextField textFieldB = new HintTextField("Another hint here");
frame.add(textFieldA, BorderLayout.NORTH);
frame.add(textFieldB, BorderLayout.CENTER);
JButton btnGetText = new JButton("Get text");
btnGetText.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String message = String.format("textFieldA='%s', textFieldB='%s'",
textFieldA.getText(), textFieldB.getText());
JOptionPane.showMessageDialog(frame, message);
}
});
frame.add(btnGetText, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.pack();
}
}
class HintTextField extends JTextField implements FocusListener {
private final String hint;
private boolean showingHint;
public HintTextField(final String hint) {
super(hint);
this.hint = hint;
this.showingHint = true;
super.addFocusListener(this);
}
#Override
public void focusGained(FocusEvent e) {
if(this.getText().isEmpty()) {
super.setText("");
showingHint = false;
}
}
#Override
public void focusLost(FocusEvent e) {
if(this.getText().isEmpty()) {
super.setText(hint);
showingHint = true;
}
}
#Override
public String getText() {
return showingHint ? "" : super.getText();
}
}
If you're still on Java 1.5, replace the this.getText().isEmpty() with this.getText().length() == 0.
Here is a simple way that looks good in any L&F:
public class HintTextField extends JTextField {
public HintTextField(String hint) {
_hint = hint;
}
#Override
public void paint(Graphics g) {
super.paint(g);
if (getText().length() == 0) {
int h = getHeight();
((Graphics2D)g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
Insets ins = getInsets();
FontMetrics fm = g.getFontMetrics();
int c0 = getBackground().getRGB();
int c1 = getForeground().getRGB();
int m = 0xfefefefe;
int c2 = ((c0 & m) >>> 1) + ((c1 & m) >>> 1);
g.setColor(new Color(c2, true));
g.drawString(_hint, ins.left, h / 2 + fm.getAscent() / 2 - 2);
}
}
private final String _hint;
}
Here is a single class copy/paste solution:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.plaf.basic.BasicTextFieldUI;
import javax.swing.text.JTextComponent;
public class HintTextFieldUI extends BasicTextFieldUI implements FocusListener {
private String hint;
private boolean hideOnFocus;
private Color color;
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
repaint();
}
private void repaint() {
if(getComponent() != null) {
getComponent().repaint();
}
}
public boolean isHideOnFocus() {
return hideOnFocus;
}
public void setHideOnFocus(boolean hideOnFocus) {
this.hideOnFocus = hideOnFocus;
repaint();
}
public String getHint() {
return hint;
}
public void setHint(String hint) {
this.hint = hint;
repaint();
}
public HintTextFieldUI(String hint) {
this(hint,false);
}
public HintTextFieldUI(String hint, boolean hideOnFocus) {
this(hint,hideOnFocus, null);
}
public HintTextFieldUI(String hint, boolean hideOnFocus, Color color) {
this.hint = hint;
this.hideOnFocus = hideOnFocus;
this.color = color;
}
#Override
protected void paintSafely(Graphics g) {
super.paintSafely(g);
JTextComponent comp = getComponent();
if(hint!=null && comp.getText().length() == 0 && (!(hideOnFocus && comp.hasFocus()))){
if(color != null) {
g.setColor(color);
} else {
g.setColor(comp.getForeground().brighter().brighter().brighter());
}
int padding = (comp.getHeight() - comp.getFont().getSize())/2;
g.drawString(hint, 2, comp.getHeight()-padding-1);
}
}
#Override
public void focusGained(FocusEvent e) {
if(hideOnFocus) repaint();
}
#Override
public void focusLost(FocusEvent e) {
if(hideOnFocus) repaint();
}
#Override
protected void installListeners() {
super.installListeners();
getComponent().addFocusListener(this);
}
#Override
protected void uninstallListeners() {
super.uninstallListeners();
getComponent().removeFocusListener(this);
}
}
Use it like this:
TextField field = new JTextField();
field.setUI(new HintTextFieldUI("Search", true));
Note that it is happening in protected void paintSafely(Graphics g).
Take a look at this one: http://code.google.com/p/xswingx/
It is not very difficult to implement it by yourself, btw. A couple of listeners and custom renderer and voila.
For any Swing component (that is, anything that extends JComponent), you can call the setToolTipText(String) method.
For more information, reference the following links:
API Documentation for setToolTipText
"How to Use Tool Tips" tutorial
Have look at WebLookAndFeel at https://github.com/mgarin/weblaf/
WebTextField txtName = new com.alee.laf.text.WebTextField();
txtName.setHideInputPromptOnFocus(false);
txtName.setInputPrompt("Name");
txtName.setInputPromptFont(new java.awt.Font("Ubuntu", 0, 18));
txtName.setInputPromptForeground(new java.awt.Color(102, 102, 102));
txtName.setInputPromptPosition(0);
If you still look for a solution, here's one that combined other answers (Bart Kiers and culmat) for your reference:
import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
public class HintTextField extends JTextField implements FocusListener
{
private String hint;
public HintTextField ()
{
this("");
}
public HintTextField(final String hint)
{
setHint(hint);
super.addFocusListener(this);
}
public void setHint(String hint)
{
this.hint = hint;
setUI(new HintTextFieldUI(hint, true));
//setText(this.hint);
}
public void focusGained(FocusEvent e)
{
if(this.getText().length() == 0)
{
super.setText("");
}
}
public void focusLost(FocusEvent e)
{
if(this.getText().length() == 0)
{
setHint(hint);
}
}
public String getText()
{
String typed = super.getText();
return typed.equals(hint)?"":typed;
}
}
class HintTextFieldUI extends javax.swing.plaf.basic.BasicTextFieldUI implements FocusListener
{
private String hint;
private boolean hideOnFocus;
private Color color;
public Color getColor()
{
return color;
}
public void setColor(Color color)
{
this.color = color;
repaint();
}
private void repaint()
{
if(getComponent() != null)
{
getComponent().repaint();
}
}
public boolean isHideOnFocus()
{
return hideOnFocus;
}
public void setHideOnFocus(boolean hideOnFocus)
{
this.hideOnFocus = hideOnFocus;
repaint();
}
public String getHint()
{
return hint;
}
public void setHint(String hint)
{
this.hint = hint;
repaint();
}
public HintTextFieldUI(String hint)
{
this(hint, false);
}
public HintTextFieldUI(String hint, boolean hideOnFocus)
{
this(hint, hideOnFocus, null);
}
public HintTextFieldUI(String hint, boolean hideOnFocus, Color color)
{
this.hint = hint;
this.hideOnFocus = hideOnFocus;
this.color = color;
}
protected void paintSafely(Graphics g)
{
super.paintSafely(g);
JTextComponent comp = getComponent();
if(hint != null && comp.getText().length() == 0 && (!(hideOnFocus && comp.hasFocus())))
{
if(color != null)
{
g.setColor(color);
}
else
{
g.setColor(Color.gray);
}
int padding = (comp.getHeight() - comp.getFont().getSize()) / 2;
g.drawString(hint, 5, comp.getHeight() - padding - 1);
}
}
public void focusGained(FocusEvent e)
{
if(hideOnFocus) repaint();
}
public void focusLost(FocusEvent e)
{
if(hideOnFocus) repaint();
}
protected void installListeners()
{
super.installListeners();
getComponent().addFocusListener(this);
}
protected void uninstallListeners()
{
super.uninstallListeners();
getComponent().removeFocusListener(this);
}
}
Usage:
HintTextField field = new HintTextField();
field.setHint("Here's a hint");
This can be achieved by using a focus listener to update the text field content.
Make the class implement the focus listener interface:
class YourClass implements FocusListener
Add a method to catch when focus is gained that blanks the field:
public void focusGained(FocusEvent e) {
if(JTextField1.getText().equals("Username")) {
JTextField1.setText("");
}
}
Add a method to catch when focus is lost to redisplay the default entry if the field was blank:
public void focusLost(FocusEvent e) {
if(JTextField1.getText().equals("")) {
JTextField1.setText("Username");
// you should prevent the form from being processed in this state
// as it will literally contain "Username" for the username
}
}
Register your class as the focus listener for text field:
textField.addFocusListener(this);
Learn more at How to Write a Focus Listener in the Java Tutorials.
Here is a fully working example based on Adam Gawne-Cain's earlier Posting. His solution is simple and actually works exceptionally well.
I've used the following text in a Grid of multiple Fields:
H__|__WWW__+__XXXX__+__WWW__|__H
this makes it possible to easily verify the x/y alignment of the hinted text.
A couple of observations:
- there are any number of solutions out there, but many only work superficially and/or are buggy
- sun.tools.jconsole.ThreadTab.PromptingTextField is a simple solution, but it only shows the prompting text when the Field doesn't have the focus & it's private, but nothing a little cut-and-paste won't fix.
The following works on JDK 8 and upwards:
import java.awt.*;
import java.util.stream.*;
import javax.swing.*;
/**
* #author DaveTheDane, based on a suggestion from Adam Gawne-Cain
*/
public final class JTextFieldPromptExample extends JFrame {
private static JTextField newPromptedJTextField (final String text, final String prompt) {
final String promptPossiblyNullButNeverWhitespace = prompt == null || prompt.trim().isEmpty() ? null : prompt;
return new JTextField(text) {
#Override
public void paintComponent(final Graphics USE_g2d_INSTEAD) {
final Graphics2D g2d = (Graphics2D) USE_g2d_INSTEAD;
super.paintComponent(g2d);
// System.out.println("Paint.: " + g2d);
if (getText().isEmpty()
&& promptPossiblyNullButNeverWhitespace != null) {
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
final Insets ins = getInsets();
final FontMetrics fm = g2d.getFontMetrics();
final int cB = getBackground().getRGB();
final int cF = getForeground().getRGB();
final int m = 0xfefefefe;
final int c2 = ((cB & m) >>> 1) + ((cF & m) >>> 1); // "for X in (A, R, G, B) {Xnew = (Xb + Xf) / 2}"
/*
* The hint text color should be halfway between the foreground and background colors so it is always gently visible.
* The variables c0,c1,m,c2 calculate the halfway color's ARGB fields simultaneously without overflowing 8 bits.
* Swing sets the Graphics' font to match the JTextField's font property before calling the "paint" method,
* so the hint font will match the JTextField's font.
* Don't think there are any side effects because Swing discards the Graphics after painting.
* Adam Gawne-Cain, Aug 6 2019 at 15:55
*/
g2d.setColor(new Color(c2, true));
g2d.drawString(promptPossiblyNullButNeverWhitespace, ins.left, getHeight() - fm.getDescent() - ins.bottom);
/*
* y Coordinate based on Descent & Bottom-inset seems to align Text spot-on.
* DaveTheDane, Apr 10 2020
*/
}
}
};
}
private static final GridBagConstraints GBC_LEFT = new GridBagConstraints();
private static final GridBagConstraints GBC_RIGHT = new GridBagConstraints();
/**/ static {
GBC_LEFT .anchor = GridBagConstraints.LINE_START;
GBC_LEFT .fill = GridBagConstraints.HORIZONTAL;
GBC_LEFT .insets = new Insets(8, 8, 0, 0);
GBC_RIGHT.gridwidth = GridBagConstraints.REMAINDER;
GBC_RIGHT.fill = GridBagConstraints.HORIZONTAL;
GBC_RIGHT.insets = new Insets(8, 8, 0, 8);
}
private <C extends Component> C addLeft (final C component) {
this .add (component);
this.gbl.setConstraints(component, GBC_LEFT);
return component;
}
private <C extends Component> C addRight(final C component) {
this .add (component);
this.gbl.setConstraints(component, GBC_RIGHT);
return component;
}
private static final String ALIGN = "H__|__WWW__+__XXXX__+__WWW__|__H";
private final GridBagLayout gbl = new GridBagLayout();
public JTextFieldPromptExample(final String title) {
super(title);
this.setLayout(gbl);
final java.util.List<JTextField> texts = Stream.of(
addLeft (newPromptedJTextField(ALIGN + ' ' + "Top-Left" , ALIGN)),
addRight(newPromptedJTextField(ALIGN + ' ' + "Top-Right" , ALIGN)),
addLeft (newPromptedJTextField(ALIGN + ' ' + "Middle-Left" , ALIGN)),
addRight(newPromptedJTextField( null , ALIGN)),
addLeft (new JTextField("x" )),
addRight(newPromptedJTextField("x", "" )),
addLeft (new JTextField(null )),
addRight(newPromptedJTextField(null, null)),
addLeft (newPromptedJTextField(ALIGN + ' ' + "Bottom-Left" , ALIGN)),
addRight(newPromptedJTextField(ALIGN + ' ' + "Bottom-Right", ALIGN)) ).collect(Collectors.toList());
final JButton button = addRight(new JButton("Get texts"));
/**/ addRight(Box.createVerticalStrut(0)); // 1 last time forces bottom inset
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setPreferredSize(new Dimension(740, 260));
this.pack();
this.setResizable(false);
this.setVisible(true);
button.addActionListener(e -> {
texts.forEach(text -> System.out.println("Text..: " + text.getText()));
});
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> new JTextFieldPromptExample("JTextField with Prompt"));
}
}

Categories