I'm developing a new look and feel and now I have a bug with the component JtextField when to use it inside the JTabbledPane component.
So the bug is this
I have this code for my JTextFieldUI, it extends BasicLookAndFell
public class MyPersonalFieldUI extends BasicTextFieldUI {
protected static final String PROPERTY_LINE_COLOR = "lineColor";
protected static final String PROPERTY_SELECTION_COLOR = "selectionColor";
protected static final String PROPERTY_SELECTION_TEXT_COLOR = "selectedTextColor";
protected static final String ProprietyPrefix = "TextField";
protected boolean drawLine;
protected JTextComponent textComponent;
protected Color background;
protected Color foreground;
protected Color activeBackground;
protected Color activeForeground;
protected Color inactiveBackground;
protected Color inactiveForeground;
protected Color colorLineInactive;
protected Color colorLineActive;
protected Color colorLine;
protected FocusListener focusListenerColorLine;
protected PropertyChangeListener propertyChangeListener;
protected PropertyChangeSupport propertyChangeSupport;
public MyPersonalFieldUI() {
this(true);
}
public MyPersonalFieldUI(boolean drawLine) {
super();
this.drawLine = drawLine;
this.focusListenerColorLine = new FocusListenerColorLine();
this.propertyChangeListener = new MaterialPropertyChangeListener();
this.propertyChangeSupport = new PropertyChangeSupport(this);
}
@Override
protected String getPropertyPrefix() {
return ProprietyPrefix;
}
public static ComponentUI createUI(JComponent c) {
return new MyPersonalFieldUI();
}
@Override
public void installUI(JComponent c) {
super.installUI(c);
this.textComponent = (JTextField) c;
}
@Override
protected void installDefaults() {
super.installDefaults();
installMyDefaults();
}
@Override
public void uninstallUI(JComponent c) {
super.uninstallUI(c);
c.setFont(null);
c.setBackground(null);
c.setForeground(null);
c.setBorder(null);
c.setCursor(null);
}
@Override
protected void uninstallDefaults() {
super.uninstallDefaults();
getComponent().setBorder(null);
}
@Override
protected void installListeners() {
super.installListeners();
getComponent().addFocusListener(focusListenerColorLine);
getComponent().addPropertyChangeListener(propertyChangeListener);
propertyChangeSupport.addPropertyChangeListener(propertyChangeListener);
}
@Override
protected void uninstallListeners() {
getComponent().removeFocusListener(focusListenerColorLine);
getComponent().removePropertyChangeListener(propertyChangeListener);
propertyChangeSupport.removePropertyChangeListener(propertyChangeListener);
super.uninstallListeners();
}
@Override
public void paintSafely(Graphics g) {
super.paintSafely(g);
paintLine(g);
}
protected void logicForChangeColorOnFocus(JComponent component, Color background, Color foreground) {
if (background == null || foreground == null) {
return;
}
JTextField textField = (JTextField) component;
if (!this.activeForeground.equals(foreground)) { //TODO this comment resolve the issue but I don't not if it introduce some bug
textField.setSelectedTextColor(inactiveForeground);
} else {
textField.setSelectedTextColor(foreground);
}
textField.setSelectionColor(background);
}
protected void installMyDefaults() {
this.background = UIManager.getColor(getPropertyPrefix() + ".background");
this.foreground = UIManager.getColor(getPropertyPrefix() + ".foreground");
this.activeBackground = UIManager.getColor(getPropertyPrefix() + ".selectionBackground");
this.activeForeground = UIManager.getColor(getPropertyPrefix() + ".selectionForeground");
this.inactiveBackground = UIManager.getColor(getPropertyPrefix() + ".inactiveBackground");
this.inactiveForeground = UIManager.getColor(getPropertyPrefix() + ".inactiveForeground");
colorLineInactive = UIManager.getColor(getPropertyPrefix() + "[Line].inactiveColor");
colorLineActive = UIManager.getColor(getPropertyPrefix() + "[Line].activeColor");
getComponent().setFont(UIManager.getFont(getPropertyPrefix() + ".font"));
colorLine = getComponent().hasFocus() && getComponent().isEditable() ? colorLineActive : colorLineInactive;
getComponent().setSelectionColor(getComponent().hasFocus() && getComponent().isEnabled() ? activeBackground : inactiveBackground);
getComponent().setSelectedTextColor(getComponent().hasFocus() && getComponent().isEnabled() ? activeForeground : inactiveForeground);
getComponent().setForeground(getComponent().hasFocus() && getComponent().isEnabled() ? activeForeground : inactiveForeground);
getComponent().setBorder(UIManager.getBorder(getPropertyPrefix() + ".border"));
}
protected void logicForPropertyChange(Color newColor, boolean isForeground) {
if (newColor == null) {
return;
}
if (isForeground && (!newColor.equals(activeForeground) && !newColor.equals(inactiveForeground))) {
this.activeForeground = newColor;
getComponent().repaint();
}
if (!isForeground && !newColor.equals(activeBackground) && !newColor.equals(inactiveBackground)) {
this.activeBackground = newColor;
getComponent().repaint();
}
}
protected void changeColorOnFocus(boolean hasFocus) {
JTextComponent c = getComponent();
if (c == null) {
return;
}
if (hasFocus && (activeBackground != null) && (activeForeground != null)) {
logicForChangeColorOnFocus(c, activeBackground, activeForeground);
//TODO create a new changePropriety
paintLine(c.getGraphics());
}
if (!hasFocus && (inactiveBackground != null) && (inactiveForeground != null)) {
logicForChangeColorOnFocus(c, inactiveBackground, inactiveForeground);
paintLine(c.getGraphics());
}
if (c.getGraphics() != null) {
c.paint(c.getGraphics());
}
}
protected synchronized void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
if ((propertyName == null || propertyName.isEmpty()) || oldValue == null || newValue == null) {
throw new IllegalArgumentException("Some property null");
}
//TODO refectoring this code
if (propertyChangeSupport == null || (oldValue != null && newValue != null && oldValue.equals(newValue))) {
return;
}
if (propertyChangeSupport == null || oldValue == newValue) {
return;
}
propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue);
}
protected void paintLine(Graphics graphics) {
if (graphics == null) {
return;
}
JTextComponent c = getComponent();
if (drawLine) {
int x = c.getInsets().left;
int y = c.getInsets().top;
int w = c.getWidth() - c.getInsets().left - c.getInsets().right;
graphics.setColor(colorLine);
graphics.fillRect(x, c.getHeight() - y, w, 1);
}
}
protected class FocusListenerColorLine implements FocusListener {
@Override
public void focusGained(FocusEvent e) {
firePropertyChange(PROPERTY_LINE_COLOR, colorLineInactive, colorLineActive);
changeColorOnFocus(true);
}
@Override
public void focusLost(FocusEvent e) {
firePropertyChange(PROPERTY_LINE_COLOR, colorLineActive, colorLineInactive);
changeColorOnFocus(false);
}
}
protected class MaterialPropertyChangeListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent pce) {
if (getComponent() == null) {
return;
}
if (pce.getPropertyName().equals(PROPERTY_SELECTION_COLOR)) {
Color newColor = (Color) pce.getNewValue();
logicForPropertyChange(newColor, false);
}
if (pce.getPropertyName().equals(PROPERTY_SELECTION_TEXT_COLOR)) {
Color newColor = (Color) pce.getNewValue();
logicForPropertyChange(newColor, true);
}
if (pce.getPropertyName().equals(PROPERTY_LINE_COLOR)) {
Color newColor = (Color) pce.getNewValue();
colorLine = newColor;
getComponent().repaint();
}
if (pce.getPropertyName().equals("background")) {
getComponent().repaint();
}
}
}
}
Sorry for my big code but I think is necessary for find my error inside it
also, this is my minimal reproducible example
public class DemoLookAndFeel extends JFrame {
static {
try {
UIManager.setLookAndFeel(new MyLookAndFeel());
} catch (UnsupportedLookAndFeelException ex) {
}
}
public void init() {
JPanel panelOne = new JPanel();
panelOne.add(new JTextField("write inside me and change the tab"));
JPanel panelTwo = new JPanel();
//panelTwo.add(new Label("Now seee the JTextField?"));
JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.add("One", panelOne);
tabbedPane.add("Two", panelTwo);
this.setContentPane(tabbedPane);
this.setSize(800,800);
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
}
private static class MyLookAndFeel extends BasicLookAndFeel {
@Override
public String getName() {
return "my look and feel";
}
@Override
public String getID() {
return "qwerty";
}
@Override
public String getDescription() {
return "";
}
@Override
public boolean isNativeLookAndFeel() {
return false;
}
@Override
public boolean isSupportedLookAndFeel() {
return true;
}
@Override
protected void initClassDefaults(UIDefaults table) {
super.initClassDefaults(table);
table.put("TextFieldUI", MyPersonalFieldUI.class.getCanonicalName());
}
@Override
protected void initSystemColorDefaults(UIDefaults table) {
super.initSystemColorDefaults(table);
table.put("TextField.background", Color.GRAY);
table.put("TextField.foreground", Color.BLACK);
table.put("TextField.inactiveForeground", Color.BLACK);
table.put("TextField.inactiveBackground", Color.GRAY);
table.put("TextField.selectionBackground", Color.BLUE);
table.put("TextField.selectionForeground", Color.WHITE);
table.put("TextField[Line].inactiveColor", Color.WHITE);
table.put("TextField[Line].activeColor", Color.BLUE);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
DemoLookAndFeel demo = new DemoLookAndFeel();
demo.init();
}
});
}
}
ps:Sorry for my Big code
The problem is on how you manipulate painting. Inside changeColorOnFocus
(called by a FocusListener
) method you have these lines:
if (c.getGraphics() != null) {
c.paint(c.getGraphics());
}
Here, you use graphics and do a kind of custom painting outside of a method that is responsible for painting in component-painting-heirarchy. I wish I could explain it better, but long story short, play with Graphics
only in methods that give them to you as an argument (and call its super
method). In an UI
class the method is paintSafely
, in a component is paintComponent
. They both give you Graphics
object hence they are proper for custom painting.
So, the solution is inside your FocusListener
to add a flag in order to know whether the component is focused, and call changeColorOnFocus
inside paintSafely(Graphics g)
method. Your UI class should be changed in the following parts (I don't paste it all since it is kind of big).
private boolean focused; //a field
protected class FocusListenerColorLine implements FocusListener {
@Override
public void focusGained(FocusEvent e) {
firePropertyChange(PROPERTY_LINE_COLOR, colorLineInactive, colorLineActive);
focused = true;
}
@Override
public void focusLost(FocusEvent e) {
firePropertyChange(PROPERTY_LINE_COLOR, colorLineActive, colorLineInactive);
focused = false;
}
}
@Override
public void paintSafely(Graphics g) {
super.paintSafely(g);
paintLine(g);
changeColorOnFocus(g);
}
protected void changeColorOnFocus(Graphics g) {
boolean hasFocus = focused;
JTextComponent c = getComponent();
if (c == null) {
return;
}
if (hasFocus && (activeBackground != null) && (activeForeground != null)) {
logicForChangeColorOnFocus(c, activeBackground, activeForeground);
//TODO create a new changePropriety
paintLine(c.getGraphics());
}
if (!hasFocus && (inactiveBackground != null) && (inactiveForeground != null)) {
logicForChangeColorOnFocus(c, inactiveBackground, inactiveForeground);
paintLine(c.getGraphics());
}
}
Preview: