How do I add a separator to a JComboBox in Java? - java

I have a JComboBox and would like to have a separator in the list of elements. How do I do this in Java?
A sample scenario where this would come in handy is when making a combobox for font-family-selection; similar to the font-family-selection-control in Word and Excel. In this case I would like to show the most-used-fonts at the top, then a separator and finally all font-families below the separator in alphabetical order.
Can anyone help me with how to do this or is this not possible in Java?

There is a pretty short tutorial with an example that shows how to use a custom ListCellRenderer on java2s
http://www.java2s.com/Code/Java/Swing-Components/BlockComboBoxExample.htm
Basically it involves inserting a known placeholder in your list model and when you detect the placeholder in the ListCellRenderer you return an instance of 'new JSeparator(JSeparator.HORIZONTAL)'

By the time I wrote and tested the code below, you probably got lot of better answers...
I don't mind as I enjoyed the experiment/learning (still a bit green on the Swing front).
[EDIT] Three years later, I am a bit less green, and I took in account the valid remarks of bobndrew. I have no problem with the key navigation that just works (perhaps it was a JVM version issue?). I improved the renderer to show highlight, though. And I use a better demo code. The accepted answer is probably better (more standard), mine is probably more flexible if you want a custom separator...
The base idea is to use a renderer for the items of the combo box. For most items, it is a simple JLabel with the text of the item. For the last recent/most used item, I decorate the JLabel with a custom border drawing a line on its bottom.
import java.awt.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class TwoPartsComboBox extends JComboBox
{
private int m_lastFirstPartIndex;
public TwoPartsComboBox(String[] itemsFirstPart, String[] itemsSecondPart)
{
super(itemsFirstPart);
m_lastFirstPartIndex = itemsFirstPart.length - 1;
for (int i = 0; i < itemsSecondPart.length; i++)
{
insertItemAt(itemsSecondPart[i], i);
}
setRenderer(new JLRenderer());
}
protected class JLRenderer extends JLabel implements ListCellRenderer
{
private JLabel m_lastFirstPart;
public JLRenderer()
{
m_lastFirstPart = new JLabel();
m_lastFirstPart.setBorder(new BottomLineBorder());
// m_lastFirstPart.setBorder(new BottomLineBorder(10, Color.BLUE));
}
#Override
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
{
if (value == null)
{
value = "Select an option";
}
JLabel label = this;
if (index == m_lastFirstPartIndex)
{
label = m_lastFirstPart;
}
label.setText(value.toString());
label.setBackground(isSelected ? list.getSelectionBackground() : list.getBackground());
label.setForeground(isSelected ? list.getSelectionForeground() : list.getForeground());
label.setOpaque(true);
return label;
}
}
}
Separator class, can be thick, with custom color, etc.
import java.awt.*;
import javax.swing.border.AbstractBorder;
/**
* Draws a line at the bottom only.
* Useful for making a separator in combo box, for example.
*/
#SuppressWarnings("serial")
class BottomLineBorder extends AbstractBorder
{
private int m_thickness;
private Color m_color;
BottomLineBorder()
{
this(1, Color.BLACK);
}
BottomLineBorder(Color color)
{
this(1, color);
}
BottomLineBorder(int thickness, Color color)
{
m_thickness = thickness;
m_color = color;
}
#Override
public void paintBorder(Component c, Graphics g,
int x, int y, int width, int height)
{
Graphics copy = g.create();
if (copy != null)
{
try
{
copy.translate(x, y);
copy.setColor(m_color);
copy.fillRect(0, height - m_thickness, width - 1, height - 1);
}
finally
{
copy.dispose();
}
}
}
#Override
public boolean isBorderOpaque()
{
return true;
}
#Override
public Insets getBorderInsets(Component c)
{
return new Insets(0, 0, m_thickness, 0);
}
#Override
public Insets getBorderInsets(Component c, Insets i)
{
i.left = i.top = i.right = 0;
i.bottom = m_thickness;
return i;
}
}
Test class:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class TwoPartsComboBoxDemo extends JFrame
{
private TwoPartsComboBox m_combo;
public TwoPartsComboBoxDemo()
{
Container cont = getContentPane();
cont.setLayout(new FlowLayout());
cont.add(new JLabel("Data: ")) ;
String[] itemsRecent = new String[] { "ichi", "ni", "san" };
String[] itemsOther = new String[] { "one", "two", "three" };
m_combo = new TwoPartsComboBox(itemsRecent, itemsOther);
m_combo.setSelectedIndex(-1);
cont.add(m_combo);
m_combo.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
String si = (String) m_combo.getSelectedItem();
System.out.println(si == null ? "No item selected" : si.toString());
}
});
// Reference, to check we have similar behavior to standard combo
JComboBox combo = new JComboBox(itemsRecent);
cont.add(combo);
}
/**
* Start the demo.
*
* #param args the command line arguments
*/
public static void main(String[] args)
{
// turn bold fonts off in metal
UIManager.put("swing.boldMetal", Boolean.FALSE);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
JFrame demoFrame = new TwoPartsComboBoxDemo();
demoFrame.setTitle("Test GUI");
demoFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
demoFrame.setSize(400, 100);
demoFrame.setVisible(true);
}
});
}
}

You can use a custom ListCellRenderer which would draw the separator items differently. See docs and a small tutorial.

Try adding this renderer. Just supply a list of index values that you want the separator to be above.
private class SeperatorComboRenderer extends DefaultListCellRenderer
{
private final float SEPARATOR_THICKNESS = 1.0f;
private final float SPACE_TOP = 2.0f;
private final float SPACE_BOTTOM = 2.0f;
private final Color SEPARATOR_COLOR = Color.DARK_GRAY;
private final List<Integer> marks;
private boolean mark;
private boolean top;
public SeperatorComboRenderer(List<Integer> marks)
{
this.marks = marks;
}
#Override
public Component getListCellRendererComponent(JList list, Object object, int index, boolean isSelected, boolean hasFocus)
{
super.getListCellRendererComponent(list, object, index, isSelected, hasFocus);
top = false;
mark = false;
marks.forEach((idx) ->
{
if(index - 1 == idx)
top = true;
if(index == idx)
mark = true;
});
return this;
}
#Override
protected void paintComponent(Graphics g)
{
if(mark)
g.translate(0, (int)(SEPARATOR_THICKNESS + SPACE_BOTTOM));
Graphics2D g2 = (Graphics2D)g;
super.paintComponent(g);
if(mark)
{
g2.setColor(SEPARATOR_COLOR);
g2.setStroke(new BasicStroke(SEPARATOR_THICKNESS));
g2.drawLine(0, 0, getWidth(), 0);
}
}
#Override
public Dimension getPreferredSize()
{
Dimension pf = super.getPreferredSize();
double height = pf.getHeight();
if(top) height += SPACE_TOP;
else if(mark) height += SEPARATOR_THICKNESS + SPACE_BOTTOM;
return new Dimension((int)pf.getWidth(), (int)height);
}
}

Related

Make running (marquee ) of JLabel in Netbeans [duplicate]

How can I implement Marquee effect in Java Swing
Here's an example using javax.swing.Timer.
import java.awt.EventQueue;
import java.awt.Font;
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.Timer;
/** #see http://stackoverflow.com/questions/3617326 */
public class MarqueeTest {
private void display() {
JFrame f = new JFrame("MarqueeTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String s = "Tomorrow, and tomorrow, and tomorrow, "
+ "creeps in this petty pace from day to day, "
+ "to the last syllable of recorded time; ... "
+ "It is a tale told by an idiot, full of "
+ "sound and fury signifying nothing.";
MarqueePanel mp = new MarqueePanel(s, 32);
f.add(mp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
mp.start();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new MarqueeTest().display();
}
});
}
}
/** Side-scroll n characters of s. */
class MarqueePanel extends JPanel implements ActionListener {
private static final int RATE = 12;
private final Timer timer = new Timer(1000 / RATE, this);
private final JLabel label = new JLabel();
private final String s;
private final int n;
private int index;
public MarqueePanel(String s, int n) {
if (s == null || n < 1) {
throw new IllegalArgumentException("Null string or n < 1");
}
StringBuilder sb = new StringBuilder(n);
for (int i = 0; i < n; i++) {
sb.append(' ');
}
this.s = sb + s + sb;
this.n = n;
label.setFont(new Font("Serif", Font.ITALIC, 36));
label.setText(sb.toString());
this.add(label);
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
#Override
public void actionPerformed(ActionEvent e) {
index++;
if (index > s.length() - n) {
index = 0;
}
label.setText(s.substring(index, index + n));
}
}
I know this is a late answer, but I just saw another question about a marquee that was closed because it was considered a duplicate of this answer.
So I thought I'd add my suggestion which takes a approach different from the other answers suggested here.
The MarqueePanel scrolls components on a panel not just text. So this allows you to take full advantage of any Swing component. A simple marquee can be used by adding a JLabel with text. A fancier marquee might use a JLabel with HTML so you can use different fonts and color for the text. You can even add a second component with an image.
Basic answer is you draw your text / graphic into a bitmap and then implement a component that paints the bitmap offset by some amount. Usually marquees / tickers scroll left so the offset increases which means the bitmap is painted at -offset. Your component runs a timer that fires periodically, incrementing the offset and invalidating itself so it repaints.
Things like wrapping are a little more complex to deal with but fairly straightforward. If the offset exceeds the bitmap width you reset it back to 0. If the offset + component width > bitmap width you paint the remainder of the component starting from the beginning of the bitmap.
The key to a decent ticker is to make the scrolling as smooth and as flicker free as possible. Therefore it may be necessary to consider double buffering the result, first painting the scrolling bit into a bitmap and then rendering that in one go rather than painting straight into the screen.
Here is some code that I threw together to get you started. I normally would take the ActionListener code and put that in some sort of MarqueeController class to keep this logic separate from the panel, but that's a different question about organizing the MVC architecture, and in a simple enough class like this it may not be so important.
There are also various animation libraries that would help you do this, but I don't normally like to include libraries into projects only to solve one problem like this.
public class MarqueePanel extends JPanel {
private JLabel textLabel;
private int panelLocation;
private ActionListener taskPerformer;
private boolean isRunning = false;
public static final int FRAMES_PER_SECOND = 24;
public static final int MOVEMENT_PER_FRAME = 5;
/**
* Class constructor creates a marquee panel.
*/
public MarqueePanel() {
this.setLayout(null);
this.textLabel = new JLabel("Scrolling Text Here");
this.panelLocation = 0;
this.taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
MarqueePanel.this.tickAnimation();
}
}
}
/**
* Starts the animation.
*/
public void start() {
this.isRunning = true;
this.tickAnimation();
}
/**
* Stops the animation.
*/
public void stop() {
this.isRunning = false;
}
/**
* Moves the label one frame to the left. If it's out of display range, move it back
* to the right, out of display range.
*/
private void tickAnimation() {
this.panelLocation -= MarqueePanel.MOVEMENT_PER_FRAME;
if (this.panelLocation < this.textLabel.getWidth())
this.panelLocaton = this.getWidth();
this.textLabel.setLocation(this.panelLocation, 0);
this.repaint();
if (this.isRunning) {
Timer t = new Timer(1000 / MarqueePanel.FRAMES_PER_SECOND, this.taskPerformer);
t.setRepeats(false);
t.start();
}
}
}
Add a JLabel to your frame or panel.
ScrollText s= new ScrollText("ello Everyone.");
jLabel3.add(s);
public class ScrollText extends JComponent {
private BufferedImage image;
private Dimension imageSize;
private volatile int currOffset;
private Thread internalThread;
private volatile boolean noStopRequested;
public ScrollText(String text) {
currOffset = 0;
buildImage(text);
setMinimumSize(imageSize);
setPreferredSize(imageSize);
setMaximumSize(imageSize);
setSize(imageSize);
noStopRequested = true;
Runnable r = new Runnable() {
public void run() {
try {
runWork();
} catch (Exception x) {
x.printStackTrace();
}
}
};
internalThread = new Thread(r, "ScrollText");
internalThread.start();
}
private void buildImage(String text) {
RenderingHints renderHints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
renderHints.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
BufferedImage scratchImage = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_RGB);
Graphics2D scratchG2 = scratchImage.createGraphics();
scratchG2.setRenderingHints(renderHints);
Font font = new Font("Serif", Font.BOLD | Font.ITALIC, 24);
FontRenderContext frc = scratchG2.getFontRenderContext();
TextLayout tl = new TextLayout(text, font, frc);
Rectangle2D textBounds = tl.getBounds();
int textWidth = (int) Math.ceil(textBounds.getWidth());
int textHeight = (int) Math.ceil(textBounds.getHeight());
int horizontalPad = 600;
int verticalPad = 10;
imageSize = new Dimension(textWidth + horizontalPad, textHeight
+ verticalPad);
image = new BufferedImage(imageSize.width, imageSize.height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = image.createGraphics();
g2.setRenderingHints(renderHints);
int baselineOffset = (verticalPad / 2) - ((int) textBounds.getY());
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, imageSize.width, imageSize.height);
g2.setColor(Color.GREEN);
tl.draw(g2, 0, baselineOffset);
// Free-up resources right away, but keep "image" for
// animation.
scratchG2.dispose();
scratchImage.flush();
g2.dispose();
}
public void paint(Graphics g) {
// Make sure to clip the edges, regardless of curr size
g.setClip(0, 0, imageSize.width, imageSize.height);
int localOffset = currOffset; // in case it changes
g.drawImage(image, -localOffset, 0, this);
g.drawImage(image, imageSize.width - localOffset, 0, this);
// draw outline
g.setColor(Color.black);
g.drawRect(0, 0, imageSize.width - 1, imageSize.height - 1);
}
private void runWork() {
while (noStopRequested) {
try {
Thread.sleep(10); // 10 frames per second
// adjust the scroll position
currOffset = (currOffset + 1) % imageSize.width;
// signal the event thread to call paint()
repaint();
} catch (InterruptedException x) {
Thread.currentThread().interrupt();
}
}
}
public void stopRequest() {
noStopRequested = false;
internalThread.interrupt();
}
public boolean isAlive() {
return internalThread.isAlive();
}
}
This is supposed to be an improvement of #camickr MarqueePanel. Please see above.
To map mouse events to the specific components added to MarqueePanel
Override add(Component comp) of MarqueePanel in order to direct all mouse events of the components
An issue here is what do do with the MouseEvents fired from the individual components.
My approach is to remove the mouse listeners form the components added and let the MarqueePanel redirect the event to the correct component.
In my case these components are supposed to be links.
#Override
public Component add(Component comp) {
comp = super.add(comp);
if(comp instanceof MouseListener)
comp.removeMouseListener((MouseListener)comp);
comp.addMouseListener(this);
return comp;
}
Then map the component x to a MarqueePanel x and finally the correct component
#Override
public void mouseClicked(MouseEvent e)
{
Component source = (Component)e.getSource();
int x = source.getX() + e.getX();
int y = source.getY();
MarqueePanel2 marqueePanel = (MarqueePanel2) ((JComponent)e.getSource()).getParent();
double x2 = marqueePanel.getWidth();
double x1 = Math.abs(marqueePanel.scrollOffset);
if(x >= x1 && x <= x2)
{
System.out.println("Bang " + x1);
Component componentAt = getComponentAt(x+marqueePanel.scrollOffset, y);
if(comp instanceof MouseListener)
((MouseListener) componentAt).mouseClicked(e);
System.out.println(componentAt.getName());
}
else
{
return;
}
//System.out.println(x);
}

JList Individually Sized Cells

I have a horizontally arranged JList. All of the items in the list are of significantly different sizes, and by default the renderer scales each item to the size of the largest item in the list. I have attempted to implement a custom renderer as follows, but each item in the list remains the same size. Any advice?
The following is the ListCellRenderer:
package ui.wizards;
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import javax.swing.SwingConstants;
public class WizHistoryCellRenderer extends JLabel
implements ListCellRenderer<String>
{
private static final long serialVersionUID = -3650608268225337416L;
JList<? extends String> list;
#Override
public Component getListCellRendererComponent(JList<? extends String> list,
String value, int index, boolean isSelected, boolean cellHasFocus)
{
this.list = list;
int width = this.getFontMetrics(this.getFont())
.stringWidth((String) value);
int height = 20;
setText(value);
if (list != null)
{
if (index == list.getSelectedIndex())
showSelected();
else
showUnselected();
}
setMaximumSize(new Dimension((int) (1.1 * width), height));
setPreferredSize(new Dimension((int) (1.1 * width), height));
setHorizontalAlignment(SwingConstants.CENTER);
setOpaque(true);
return this;
}
private void showSelected()
{
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
private void showUnselected()
{
setBackground(list.getBackground());
setForeground(list.getForeground());
}
}
Try calling the JList method setFixedCellHeight, with -1 as the argument. This causes JList to use the preferred height returned by any custom renderer when drawing the list items.
I think you could try to subclass javax.swing.plaf.basic.BasicListUI and override protected void paintCell(Graphics g, int row, Rectangle rowBounds, ListCellRenderer cellRenderer, ListModel dataModel, ListSelectionModel selModel, int leadIndex) and maybe some other methods performing cell size calculation.
Then use JList.setUI(ListUI) to apply your custom UI. The UI is responsible for drawing the JList using ListCellRenders. Please excuse that I do not provide a complete example as it is presumably a lot of work tweaking all aspects of the custom drawing process.
You will need 3 things:
A scale factor for each individual cell. You may for example add a scale factor variable of type double in each entry of the list.
A custom ListCellRenderer.
Make the method BasicListUI#updateLayoutState() public.
Follows self-contained code (read the comments for more info):
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.Icon;
import javax.swing.JButton;
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.plaf.basic.BasicListUI;
public class JListIndipendentCellSizes {
//Configuration constants:
private static final int ICON_SIZE = 20;
private static final double SCALE_STEP_SIZE = 0.125; //Smaller values of this makes zooming slower. Greater values makes zooming faster.
//Simple Icon implementation for demonstration purposes.
public static class TJIcon implements Icon {
private final Color rectColor, ovalColor;
public TJIcon(final Color rectColor, final Color ovalColor) {
this.rectColor = rectColor;
this.ovalColor = ovalColor;
}
#Override
public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
g.setColor(rectColor);
g.fillRect(x, y, getIconWidth(), getIconHeight());
g.setColor(ovalColor);
g.fillOval(x, y, getIconWidth(), getIconHeight());
}
#Override public int getIconWidth() { return ICON_SIZE; }
#Override public int getIconHeight() { return ICON_SIZE; }
}
//A simple list entry. Contains a text, an icon and (on top of them all) the scale factor:
public static class TJListEntry {
private final String text;
private final Icon icon;
private double scaleFactor;
public TJListEntry(final String text,
final Icon icon) {
this.text = text;
this.icon = icon;
scaleFactor = 1;
}
public String getText() {
return text;
}
public Icon getIcon() {
return icon;
}
public double getScaleFactor() {
return scaleFactor;
}
public void zoomIn() {
scaleFactor = scaleFactor + SCALE_STEP_SIZE;
}
public void zoomOut() {
scaleFactor = Math.max(scaleFactor - SCALE_STEP_SIZE, SCALE_STEP_SIZE); //Do not allow underflow.
}
public void resetZoom() {
scaleFactor = 1;
}
}
public static class TJListCellRenderer extends JLabel implements ListCellRenderer<TJListEntry> {
private double currentScaleFactor;
public TJListCellRenderer() {
//Ensure every pixel is painted starting from the top-left corner of the label:
super.setVerticalAlignment(JLabel.TOP);
super.setHorizontalAlignment(JLabel.LEFT);
//We need to do this, because the scaling in paintComponent() is also relative to the top-left corner.
}
#Override
public void paintComponent(final Graphics g) {
//setRenderingHints here? Probably for ANTIALIAS...
((Graphics2D)g).scale(currentScaleFactor, currentScaleFactor); //Let's scale everything that is painted afterwards:
super.paintComponent(g); //Let's paint the (scaled) JLabel!
}
#Override
public Dimension getPreferredSize() {
final Dimension superPrefDim = super.getPreferredSize(); //Handles automatically insets, icon size, text font, etc.
final double w = superPrefDim.width * currentScaleFactor, //And we just scale the preferred size.
h = superPrefDim.height * currentScaleFactor; //And we just scale the preferred size.
return new Dimension((int)w + 1, (int)h + 1); //Add one extra pixel to spare.
}
#Override
public Component getListCellRendererComponent(final JList<? extends TJListEntry> list, final TJListEntry value, final int index, final boolean isSelected, final boolean cellHasFocus) {
currentScaleFactor = value.getScaleFactor(); //Probably the most important step.
setIcon(value.getIcon()); //Could be a loaded ImageIcon here (i.e. image)!
setText(value.getText());
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setOpaque(true);
return this;
}
}
public static class TJListUI extends BasicListUI {
#Override
public void updateLayoutState() {
super.updateLayoutState(); //Just make the following method public.
/*Note: this is not really needed here:
The method could remain protected, but in the case you want this
code to be a bit more reusable, then you shall make it public.*/
}
}
public static void main(final String[] args) {
final TJListEntry[] entries = new TJListEntry[]{new TJListEntry("This is a sample text.", new TJIcon(Color.BLACK, Color.WHITE)),
new TJListEntry("This is a longer sample text.", new TJIcon(Color.GREEN, Color.RED)),
new TJListEntry("Small text", new TJIcon(Color.LIGHT_GRAY, Color.BLUE))};
final TJListUI ui = new TJListUI();
final JList<TJListEntry> list = new JList<>(entries);
list.setUI(ui); //Important step! Without setting our UI, it won't work (at least as I have looked for).
list.setCellRenderer(new TJListCellRenderer()); //Important step! Without setting our custom ListCellRenderer you will have to implement your own ListUI probably.
final JScrollPane scroll = new JScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
final JButton buttonZoomIn = new JButton("Zoom in selected cells"),
buttonZoomOut = new JButton("Zoom out selected cells"),
buttonResetZoom = new JButton("Reset zoom of selected cells");
buttonZoomIn.addActionListener(e -> {
for (final int i: list.getSelectedIndices())
list.getModel().getElementAt(i).zoomIn();
ui.updateLayoutState(); //Read the preferred sizes from the cell renderer.
list.revalidate(); //Update the JScrollPane.
list.repaint(); //Repaint the list.
});
buttonZoomOut.addActionListener(e -> {
for (final int i: list.getSelectedIndices())
list.getModel().getElementAt(i).zoomOut();
ui.updateLayoutState(); //Read the preferred sizes from the cell renderer.
list.revalidate(); //Update the JScrollPane.
list.repaint(); //Repaint the list.
});
buttonResetZoom.addActionListener(e -> {
for (final int i: list.getSelectedIndices())
list.getModel().getElementAt(i).resetZoom();
ui.updateLayoutState(); //Read the preferred sizes from the cell renderer.
list.revalidate(); //Update the JScrollPane.
list.repaint(); //Repaint the list.
});
final JPanel buttons = new JPanel(); //FlowLayout.
buttons.add(buttonZoomIn);
buttons.add(buttonZoomOut);
buttons.add(buttonResetZoom);
final JPanel panel = new JPanel(new BorderLayout());
panel.add(buttons, BorderLayout.PAGE_START);
panel.add(scroll, BorderLayout.CENTER);
final JFrame frame = new JFrame("Independent JList cell sizes demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
And a screenshot:
To run it on your own, just select at least one index, and play with the buttons.

Passing Data between Java Classes

I'm having trouble retrieving the variable from my Colour class after i send the index from the GUI after selecting a colour from the drop down list. I can send the index fine and retrieve it from the HashMap, i know this because i use System.out.println to check. Basically my questions are, where have i gone wrong? and What do i need to remember to make sure i don't have this trouble again? Edit: forgot to mention, the button sending the index is in a seperate JPanel which is used for the UI components(buttons and combo boxes).
//edit
class UIPanel extends JPanel{
public MainPanel gpanel;
public Integer data;
public Color colval;
public Colour col;
public UIPanel(MainPanel panel) {
col = new Colour();
gpanel = panel;
Box btnBox = Box.createHorizontalBox();
btnBox.add(setBtn = new JButton());
btnBox.add(Box.createHorizontalGlue());
JButton setBtn = new JButton("Set");
final DefaultComboBoxModel colour = new DefaultComboBoxModel();
colour.addElement("Red");
final JComboBox colours = new JComboBox(colour);
JScrollPane colourScroll = new JScrollPane(colours);
btnBox.setSize(300, 100);
btnBox.add(Box.createHorizontalGlue());
add(btnBox, BorderLayout.NORTH);
//end of edit
Button to send Index from GUI class to Colour class
setBtn.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
data= colours.getSelectedIndex();
col.setCol(data);
}
});
Colour Class where hashmap with the list of colours in.
public class Colour{
public Color colVal;
HashMap<Integer, Color> map = new HashMap<Integer, Color>();
public Colour() {
map.put(0, Color.RED);
map.put(1, Color.BLUE);
map.put(2, Color.YELLOW);
map.put(3, Color.GREEN);
}
public Color setCol(Integer data) {
//Color colours;
colVal = map.get(data);
System.out.println("colour" + colVal);
return colVal;
}
public Color getColVal() {
return colVal;
}
And the paint area on the GUI class where the colour will be sent to from the colour class
class MainPanel extends JPanel{
//private Colour col;
int px, py;
//radius
public Color colvals;
public Colour col;
public MainPanel() {
col = new Colour();
this.addMouseMotionListener(new MouseMotionAdapter() {
// store drag coordinates and repaint
public void mouseDragged( MouseEvent event )
{
px = event.getX();
py = event.getY();
repaint();
}
}); // end call to addMouseMotionListener
}
public void paint( Graphics g ) {
g.setColor(col.colVal);//This is where the colour value will be placed
System.out.println(col.colVal);
g.fillOval( px, py, 15, 15 );
}
}
I'm Probably missing something stupid out but I cant seem to figure it out.
P.S: How complicated will it be to make a Vignere Cipher Application?
JComboBox can be used to directly use any Objects as items with only one little thing to consider: It will use the toString for the label to display. (see JComboBox javadoc - Providing a Custom Renderer).
But instead of using a custom renderer I always preferred to write a very simple helper class once - let's call it ComboBoxItem - that is reusable for any kind of data.
public class ComboBoxItem<T>
{
private T value;
private String label;
public ComboBoxItem(T value, String label)
{
this.value = value;
this.label = label;
}
public T getValue()
{
return this.value;
}
public String getLabel()
{
return this.label;
}
// important! since this is the workaround ;-)
public String toString()
{
return this.label; // or whatever you like
}
}
And then populate the JComboBox with ComboBoxItems instead of String values:
In your code instead of
final DefaultComboBoxModel colour = new DefaultComboBoxModel();
colour.addElement("Red");
colour.addElement("Blue");
colour.addElement("Yellow");
colour.addElement("Green");
colours = new JComboBox(colourValues);
... you will use
final DefaultComboBoxModel colour = new DefaultComboBoxModel();
colour.addElement(new ComboBoxItem<Color>(Color.RED, "Red"));
colour.addElement(new ComboBoxItem<Color>(Color.BLUE, "Blue"));
colour.addElement(new ComboBoxItem<Color>(Color.YELLOW, "Yellow"));
colour.addElement(new ComboBoxItem<Color>(Color.GREEN, "Green"));
colours = new JComboBox(colourValues);
This will make the select contain ComboBoxItems as values which you can simply access by doing the following:
// instead of getSelectedIndex()
ComboBoxItem<Color> item = (ComboBoxItem) colours.getSelectedItem();
Color c = item.getValue();
The same procedure can then be reused for any other kind of values - even complex ones.
Note: If you have a data object with an appropriate toString() representation anyway, you can of course simply use it as a value for the select.
Note2: If a string representation is not enough (e.g. you want to display the color along with the name), have a look at ListCellRenderer which is able to display the item in any desired way (by returning an arbitrary JComponent).
Your setCol(...) method inside of the Colour class should be getCol(...) since it's functioning as a getter:
public class Colour{
public Color colVal;
HashMap<Integer, Color> map = new HashMap<Integer, Color>();
public Colour() {
map.put(0, Color.RED);
map.put(1, Color.BLUE);
map.put(2, Color.YELLOW);
map.put(3, Color.GREEN);
}
// **** change name ****
public Color getCol(Integer data) {
//Color colours;
colVal = map.get(data);
System.out.println("colour" + colVal);
return colVal;
}
// **** not sure you need this method
public Color getColVal() {
return colVal;
}
and in your ActionListener, you retrieve the color but never do anything with it. It should be:
public void actionPerformed(ActionEvent e) {
data = colours.getSelectedIndex();
Color color = col.getCol(data); // note name change
// use Color variable, color, somehow here
mainPanel.setColVals(color); // something like this perhaps
mainPanel.repaint(); // to tell the JVM to repaint the JPanel
}
Also note that in your JPanel class override you should override the paintComponent method, not the paint method, and don't forget to call the super's method.
i.e.,
public void setColVals(Color colVals) {
this.colVals = colVals;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(colVal);
// System.out.println(colVal);
g.fillOval( px, py, 15, 15 );
}
Edit a better answer:
Get rid of Colour entirely.
Use an enum to match your Color to a String and create a JComboBox model out of that enum.
Using an enum will prevent you're having to use magic numbers with the risk that the wrong number has been used, a number that doesn't match a color.
Also, by using an enum, it is trivial to change your code and add more colors. Just add a new item to the enum, and the rest of the program will adapt to the change.
Add a PropertyChangeListener from the MainPanel to the UIPanel and listen for changes to its "bound" Color property.
Use RenderingHints with a Graphics2D object to smooth out the jaggies from your circle drawing.
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
public class ColorListenerPanel extends JPanel {
public ColorListenerPanel() {
UIPanel uiPanel = new UIPanel();
MainPanel mainPanel = new MainPanel(uiPanel);
setLayout(new BorderLayout());
add(mainPanel, BorderLayout.CENTER);
add(uiPanel, BorderLayout.PAGE_START);
}
private static void createAndShowGui() {
ColorListenerPanel mainPanel = new ColorListenerPanel();
JFrame frame = new JFrame("ColorListenerPanel");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
class MainPanel extends JPanel {
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final int OVAL_WIDTH = 16;
private int px, py;
private Color color = MyColors.values()[0].getColor();
public MainPanel(UIPanel uiPanel) {
this.addMouseMotionListener(new MouseMotionAdapter() {
// store drag coordinates and repaint
public void mouseDragged(MouseEvent event) {
px = event.getX();
py = event.getY();
repaint();
}
});
uiPanel.addPropertyChangeListener(UIPanel.COLOR, new UiListener());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(color);
g.fillOval(px - OVAL_WIDTH / 2, py - OVAL_WIDTH / 2, OVAL_WIDTH, OVAL_WIDTH);
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class UiListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
// not really needed since our listener is added using
// this property name
if (!UIPanel.COLOR.equals(pcEvt.getPropertyName())) {
return;
}
color = (Color) pcEvt.getNewValue();
repaint();
}
}
}
enum MyColors {
RED("Red", Color.RED),
BLUE("Blue", Color.BLUE),
YELLOW("Yellow", Color.YELLOW),
GREEN("Green", Color.GREEN);
private String name;
private Color color;
private MyColors(String name, Color color) {
this.name = name;
this.color = color;
}
public String getName() {
return name;
}
public Color getColor() {
return color;
}
#Override
public String toString() {
return name;
}
}
class UIPanel extends JPanel {
public static final String COLOR = "color";
private MainPanel gpanel;
private Integer data;
private Color color;
private DefaultComboBoxModel<MyColors> comboModel = new DefaultComboBoxModel<>();
private JComboBox<MyColors> colorsCombo = new JComboBox<>(comboModel);
SetColorAction setColorAction = new SetColorAction("Set", KeyEvent.VK_S);
public UIPanel() {
setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
add(colorsCombo);
add(Box.createHorizontalStrut(5));
add(new JButton(setColorAction));
colorsCombo.addActionListener(setColorAction);
add(Box.createHorizontalGlue());
for (MyColors myColor : MyColors.values()) {
comboModel.addElement(myColor);
}
}
public void setColor(Color color) {
Color oldValue = this.color;
Color newValue = color;
this.color = color;
firePropertyChange(COLOR, oldValue, newValue);
}
private class SetColorAction extends AbstractAction {
public SetColorAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent evt) {
MyColors selection = (MyColors) colorsCombo.getSelectedItem();
if (selection != null) {
setColor(selection.getColor());
}
}
}
}
In this Function
public void paint( Graphics g ) {
g.setColor(col.colVal);//This is where the colour value will be placed
System.out.println(col.colVal);
g.fillOval( px, py, 15, 15 );
}
You are using g.setColor(col.colVal); col.colVal will give you the default value assigned to it probably null ,You should use col.getColVal() as this is the getter method you have created for the colVal attribute.
And also in the setter declaration public Color setCol(Integer data) the return type you are using is Color but when you are using this function in your GUI class without a Color variable which can accept a Value returned by your setter .
I don't understand the need of returning values from a Setter method.

JTable- Resize row header when row is resized

I have a grid (JTable) that looks like MS Excel's grid. I want to allow the user to resize rows and columns. For the columns I simply used this :
grid.getTableHeader().setResizingAllowed(true);
And for the rows I took the TableRowResizer class from here and which I'm using like this :
new TableRowResizer(grid);
This works fine. However, I've one problem : when resizing a row the row header is not resized too.
Here's how I made the row headers :
AbstractListModel lm = null;
lm = new TableListModel(grid);
final JList list = new JList(lm);
list.setFixedCellWidth(60);
list.setFixedCellHeight(grid.getRowHeight());
list.setCellRenderer(new TableRowHeaderRenderer(grid));
list.setBackground(grid.getTableHeader().getBackground());
scrollPane.setRowHeaderView(list);
Here's the TableRowHeaderRenderer class :
class TableRowHeaderRenderer extends JLabel implements ListCellRenderer {
private JTable table;
public TableRowHeaderRenderer(JTable table)
{
this.table = table;
JTableHeader header = table.getTableHeader();
setOpaque(true);
setBorder(BorderFactory.createEtchedBorder());
setHorizontalAlignment(CENTER);
setForeground(header.getForeground());
setBackground(header.getBackground());
setFont(header.getFont());
}
public Component getListCellRendererComponent(JList list,
Object value, int index, boolean isSelected, boolean cellHasFocus)
{
Color bg = UIManager.getColor("TableHeader.background");
int selectedrow = table.getSelectedRow();
if (selectedrow==index) bg = new Color(107, 142, 35);
setBackground(bg);
setText("" + Grid.getRowName(index));
return this;
}
}
And this is the TableListModelclass :
class TableListModel extends AbstractListModel{
private JTable mytable;
public TableListModel(JTable table) {
super();
mytable = table;
}
public int getSize() {
return mytable.getRowCount();
}
public Object getElementAt(int index) {
return "" + index;
}
}
Check out the Row Number Table. It uses a JTable (instead of a JList) to render the row numbers. Therefore you can keep the row heights in sync with the main table.
However, I can't get the row header to repaint automatically when the row height of the main table is changed since no event is fired when an individual row height is changed. So you will also need to modify the resizing code to look something like:
table.setRowHeight(resizingRow, newHeight);
JScrollPane scrollPane = (JScrollPane)SwingUtilities.getAncestorOfClass(JScrollPane.class, table);
scrollPane.getRowHeader().repaint();
I know this is an old question and have already accepted answer but I find the accepted answer have a performance issue when you implement a resizable row header in which a user can resize the row by just dragging the line in between rows of the header, just like in excel. When dragged outside the header's bound, it begins lagging too much and I can't find any alternative good performance implementation of this so I made my own header and for those who want to implement a resizable row header, you might wanna check this out.
Create an interface that will be use to listen for rows selection in the table.
public interface TableSelectionListener {
public void onRowSelected(int selectedRow);
public void onMultipleRowsSelected(Map<Integer, Integer> rows);
}
make a RowTableHeader class and extend the JComponent, implement the interface you made and some other listeners for the table.
/**
* Initialize this class after setting the table's model
* #author Rene Tajos Jr.
*/
public class TableRowHeader extends JComponent implements ChangeListener, TableModelListener, TableSelectionListener {
private JTable mTable = new JTable();
private final int mRows;
private int mViewportPos = 0;
private final Color mGridColor;
private Color mFontColor = GradeUtils.Colors.darkBlueColor;
private final Color mSelectedRowColor = GradeUtils.Colors.semiCreamWhiteBlueColor;
private final Color mBgColor = new Color(247,245,251);
private int mSelectedRow = -1;
private Map<Integer, Integer> mRowsSelected = new HashMap<>();
private Font mFont = GradeUtils.getDefaultFont(12);
private Map<Integer> resizePoints = new HashMap<>();
private final MouseInputAdapter inputAdapter = new MouseInputAdapter() {
private int mMouseY = -1;
private final int MIN_ROW_HEIGHT = 23;
private int mRowToResize;
private boolean isOnResizingPoint = false;
#Override
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
if (resizePoints.containsKey((int)p.getY())) {
isOnResizingPoint = true;
GradeUtils.setCustomCursor(TableRowHeader.this, GradeUtils.resizeVerticalCursorStr);
return;
}
isOnResizingPoint = false;
setCursor(Cursor.getDefaultCursor());
}
#Override
public void mouseDragged(MouseEvent e) {
if (!isOnResizingPoint)
return;
int y = e.getY();
if (mMouseY != -1) {
int elapse = y - mMouseY;
int oldRowHeight = mTable.getRowHeight(mRowToResize);
int rowHeight = Math.max(MIN_ROW_HEIGHT, oldRowHeight + elapse);
mTable.setRowHeight(mRowToResize, rowHeight);
}
mMouseY = y;
}
#Override
public void mouseReleased(MouseEvent e) {
mMouseY = -1;
isOnResizingPoint = false;
}
#Override
public void mousePressed(MouseEvent e) {
if (isOnResizingPoint) {
mMouseY = e.getY();
// region: get the row point to resize
final Point p = e.getPoint();
p.y += mViewportPos;
mRowToResize = mTable.rowAtPoint(_getRowPoint(p, 5));
// region end
}
}
/**
* Locate the row point to resize
* #param p The event Point
* #param i The number difference of where to locate the row
*/
private Point _getRowPoint(Point p, int i) {
if (!resizePoints.containsKey(p.y -= i)) {
p.y -= i;
return p;
}
return _getRowPoint(p, i-1);
}
};
public TableRowHeader(JTable table) {
mTable = table;
mRows = table.getRowCount();
mGridColor = mTable.getGridColor();
((TajosTable)mTable).addTableSelectionListener(this);
JViewport v = (JViewport) mTable.getParent();
v.addChangeListener(this);
mTable.getModel().addTableModelListener( this );
setPreferredSize(new Dimension(30, HEIGHT));
setOpaque(true);
setBackground(Color.WHITE);
addMouseListener(inputAdapter);
addMouseMotionListener(inputAdapter);
}
/**
* Update the row resize points
*/
private void _updateResizePoints(int y) {
if (!resizePoints.isEmpty())
resizePoints.clear();
int nexPoint = y;
for (int i=0; i<mTable.getRowCount(); i++) {
int resizePointMinThreshold = nexPoint + mTable.getRowHeight(i) - 2;
int resizePointMaxThreshold = nexPoint + mTable.getRowHeight(i) + 2;
for (int j=resizePointMinThreshold; j<=resizePointMaxThreshold; j++)
resizePoints.put(j, j);
nexPoint += mTable.getRowHeight(i);
}
}
#Override
public void setForeground(Color fg) {
mFontColor = fg;
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
int y = -1 - mViewportPos;
_updateResizePoints(y);
// loop in every rows to draw row header
for (int row=0; row<mRows; row++) {
int rowHeight = mTable.getRowHeight(row);
g2d.setColor(mGridColor);
if (row != mRows-1 && row == 0) { // draw row at index 0
// region: draw background
if (row == mSelectedRow || mRowsSelected.containsKey(row))
g2d.setColor(mSelectedRowColor);
else
g2d.setColor(mBgColor);
g2d.fillRect(0, y+1, getPreferredSize().width-1, rowHeight-1);
// region end
// region: draw borders
g2d.setColor(mGridColor);
g2d.drawRect(0, y+1, getPreferredSize().width-1, rowHeight-1);
// region end
} else { // draw the rest of the rows
// region: draw background
if (row == mSelectedRow || mRowsSelected.contains(row))
g2d.setColor(mSelectedRowColor);
else
g2d.setColor(mBgColor);
g2d.fillRect(0, y, getPreferredSize().width-1, rowHeight);
// region end
// region: draw borders
g2d.setColor(mGridColor);
g2d.drawRect(0, y, getPreferredSize().width-1, rowHeight);
// region end
}
// region: draw text with CENTER ALIGNMENT
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setColor(mFontColor);
g2d.setFont(mFont);
String str = String.valueOf(row+1);
FontMetrics fm = getFontMetrics(mFont);
Rectangle2D textRect = fm.getStringBounds(str, g2d);
int textX = ((getPreferredSize().width-1) - (int)textRect.getWidth()) / 2;
int diffY = (rowHeight - (int)textRect.getHeight()) / 2;
int textY = y + (rowHeight - diffY);
g2d.drawString(str, textX, textY - 1);
// region end
y += rowHeight;
}
g2d.dispose();
}
/**
* Implement the ChangeListener
*/
#Override
public void stateChanged(ChangeEvent e)
{
// Keep the view of this row header in sync with the table
JViewport viewport = (JViewport) e.getSource();
mViewportPos = viewport.getViewPosition().y;
}
/**
* Listens for changes in the table
*/
#Override
public void tableChanged(TableModelEvent e)
{
revalidate();
}
/**
* Listens for single row selection
* #param selectedRow The selected row
*/
#Override
public void onRowSelected(int selectedRow) {
mSelectedRow = selectedRow;
if (!mRowsSelected.isEmpty())
mRowsSelected.clear();
revalidate();
repaint();
}
/**
* Listens for multiple row selection
* #param rows The selected rows
*/
#Override
public void onMultipleRowsSelected(Map<Integer, Integer> rows) {
mSelectedRow = -1;
mRowsSelected = rows;
revalidate();
repaint();
}
}
After that, you need to create a class that extends Jtable where you can add a mouselisteners that will listen every time the user selects/multiple selects row/s
public class TajosTable extends JTable {
............
private TableSelectionListener mTableSelectionListener;
private final MouseInputAdapter mouseListener = new MouseInputAdapter() {
private final int TRUE = 1, FALSE = 0;
private int isDraggingMouse;
#Override
public void mousePressed(MouseEvent e) {
final int row = rowAtPoint(e.getPoint());
final int col = columnAtPoint(e.getPoint());
mTableSelectionListener.onRowSelected(row);
}
#Override
public void mouseDragged(MouseEvent e) {
isDraggingMouse = TRUE;
final int[] rowsSelected = getSelectedRows();
final int[] colsSelected = getSelectedColumns();
Map<Integer, Integer> map = new HashMap<>();
for (int row : rowsSelected)
map.put(row, row);
mTableSelectionListener.onMultipleRowsSelected(map);
}
#Override
public void mouseReleased(MouseEvent e) {
isDraggingMouse = FALSE;
}
};
public TajosTable() {
super();
setTableHeader(null);
setColumnSelectionAllowed(true);
// add the mouse listener
addMouseListener(mouseListener);
addMouseMotionListener(mouseListener);
}
............
// and of course create a method that will attach the selection listener
public void addTableSelectionListener(TableSelectionListener lstener) {
mTableSelectionListener = lstener;
}
}
And in your main() {}, always attach this Row Header to the scrollpane AFTER setting the table's model
table.setModel(new YourTableModel(rows, cols));
tableScrollPane.setRowHeaderView(new TableRowHeader(table));
That's it. You can further modify it to suit your needs.
Here is the result, and now when I exited my cursor while resizing a row outside the bounds of my custom made row header. I can't see any lags now.
Here's the result in GIF image

Decorating a JTextField with an image and hint

I'm trying to create some nicer looking JTextFields with an image and a hint. To do this I made a decorator that overrides the paintComponent method. The reason I used a decorator is that I wanted to apply it to other types of JTextField such as JPasswordField.
Here is what I've made so far;
The problem as seen in the form on the left is that, even though I have used a JPasswordField the paintComponent seems to ignore what I assume is the passwords paintComponent which presumably does the password masking symbols.
So the question is, how can I avoid duplicating the code for JTextFields and JPasswordFields but still have the different functionality such as password masking.
This is the decorator code;
public class JTextFieldHint extends JTextField implements FocusListener{
private JTextField jtf;
private Icon icon;
private String hint;
private Insets dummyInsets;
public JTextFieldHint(JTextField jtf, String icon, String hint){
this.jtf = jtf;
setIcon(createImageIcon("icons/"+icon+".png",icon));
this.hint = hint;
Border border = UIManager.getBorder("TextField.border");
JTextField dummy = new JTextField();
this.dummyInsets = border.getBorderInsets(dummy);
addFocusListener(this);
}
public void setIcon(Icon newIcon){
this.icon = newIcon;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int textX = 2;
if(this.icon!=null){
int iconWidth = icon.getIconWidth();
int iconHeight = icon.getIconHeight();
int x = dummyInsets.left + 5;
textX = x+iconWidth+2;
int y = (this.getHeight() - iconHeight)/2;
icon.paintIcon(this, g, x, y);
}
setMargin(new Insets(2, textX, 2, 2));
if ( this.getText().equals("")) {
int width = this.getWidth();
int height = this.getHeight();
Font prev = g.getFont();
Font italic = prev.deriveFont(Font.ITALIC);
Color prevColor = g.getColor();
g.setFont(italic);
g.setColor(UIManager.getColor("textInactiveText"));
int h = g.getFontMetrics().getHeight();
int textBottom = (height - h) / 2 + h - 4;
int x = this.getInsets().left;
Graphics2D g2d = (Graphics2D) g;
RenderingHints hints = g2d.getRenderingHints();
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.drawString(hint, x, textBottom);
g2d.setRenderingHints(hints);
g.setFont(prev);
g.setColor(prevColor);
}
}
protected ImageIcon createImageIcon(String path, String description) {
java.net.URL imgURL = getClass().getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL, description);
} else {
System.err.println("Couldn't find file: " + path);
return null;
}
}
#Override
public void focusGained(FocusEvent arg0) {
this.repaint();
}
#Override
public void focusLost(FocusEvent arg0) {
this.repaint();
}
}
And this is where I create the fields;
JTextField usernameField = new JTextFieldHint(new JTextField(),"user_green","Username");
JTextField passwordField = new JTextFieldHint(new JPasswordField(),"bullet_key","Password");
Hopefully i've not went completely off in the wrong direction here!
Thanks!
EDIT : Again the more I look at it, it is obvious that calling super.paintComponent(g) is going to call the JTextFields paintcomponent, but I can't see how to solve this without duplicating the code.
Text Prompt works with a JPasswordField.
One difference is that the displayed icon disappears when text is entered. If you want the icon to be permanent then I suggest you create a custom "IconBorder* class to paint an Icon rather then do custom painting in the paintComponent() method.
You approach will not work unless you duplicate the code for both JTextField and JPasswordField.
Edit:
Actually you don't need to create a custom IconBorder. The MatteBorder supports the painting of an Icon in a Border.
In order to paint an icon inside a text field you need to add some insets.
You don't want to hard-code insets in your component but just add a little bit of space for the icon, letting clients and subclasses to set their own.
In the figure above I painted original insets in green and additional insets in red. First thing you want to extend JTextField. We keep track of two things: the original insets (the green ones) mBorder, and the icon.
public class IconTextField extends JTextField {
private Border mBorder;
private Icon mIcon;
// ...
}
Then you need to override setBorder() method.
#Override
public void setBorder(Border border) {
mBorder = border;
if (mIcon == null) {
super.setBorder(border);
} else {
Border margin = BorderFactory.createEmptyBorder(0, mIcon.getIconWidth() + ICON_SPACING, 0, 0);
Border compound = BorderFactory.createCompoundBorder(border, margin);
super.setBorder(compound);
}
}
Here, if we have an icon (the field mIcon is not null), we add our additional insets using a compound border. Then, you should also override the paintComponent() method.
#Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
if (mIcon != null) {
Insets iconInsets = mBorder.getBorderInsets(this);
mIcon.paintIcon(this, graphics, iconInsets.left, iconInsets.top);
}
}
Finally, you need a setIcon() method.
public void setIcon(Icon icon) {
mIcon = icon;
resetBorder();
}
private void resetBorder() {
setBorder(mBorder);
}
What we are doing here is saving the icon and recalculating the borders.
If you want to do the same same thing with JPasswordField, the most elegant thing is probably to create a helper class with all the methods discussed above.
class IconTextComponentHelper {
private static final int ICON_SPACING = 4;
private Border mBorder;
private Icon mIcon;
private Border mOrigBorder;
private JTextComponent mTextComponent;
IconTextComponentHelper(JTextComponent component) {
mTextComponent = component;
mOrigBorder = component.getBorder();
mBorder = mOrigBorder;
}
Border getBorder() {
return mBorder;
}
void onPaintComponent(Graphics g) {
if (mIcon != null) {
Insets iconInsets = mOrigBorder.getBorderInsets(mTextComponent);
mIcon.paintIcon(mTextComponent, g, iconInsets.left, iconInsets.top);
}
}
void onSetBorder(Border border) {
mOrigBorder = border;
if (mIcon == null) {
mBorder = border;
} else {
Border margin = BorderFactory.createEmptyBorder(0, mIcon.getIconWidth() + ICON_SPACING, 0, 0);
mBorder = BorderFactory.createCompoundBorder(border, margin);
}
}
void onSetIcon(Icon icon) {
mIcon = icon;
resetBorder();
}
private void resetBorder() {
mTextComponent.setBorder(mOrigBorder);
}
}
And use it like this:
public class IconTextField extends JTextField {
private IconTextComponentHelper mHelper = new IconTextComponentHelper(this);
public IconTextField() {
super();
}
public IconTextField(int cols) {
super(cols);
}
private IconTextComponentHelper getHelper() {
if (mHelper == null)
mHelper = new IconTextComponentHelper(this);
return mHelper;
}
#Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
getHelper().onPaintComponent(graphics);
}
public void setIcon(Icon icon) {
getHelper().onSetIcon(icon);
}
public void setIconSpacing(int spacing) {
getHelper().onSetIconSpacing(spacing);
}
#Override
public void setBorder(Border border) {
getHelper().onSetBorder(border);
super.setBorder(getHelper().getBorder());
}
}

Categories

Resources