Here is my probleme. I have a JComponent in the middle of a slider, itself in the middle of the BorderLayout in a frame. I use my JComponent to draw/write things in it.
My problem came when I implemented a JColorChooser in one of the sides of the border layout. It is supposed to change the color of the drawings. Once I selected a color on the JColorChooser, my JComponent stopped responding to KeyEvents and I couldn't write anything anymore.
Here my code simplified :
My main :
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
public class TestPhotoComponent {
public static void main(String[] args) {
JFrame frame = new JFrame("PhotoComponent test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel p1 = new JPanel();
p1.setPreferredSize(new Dimension(Integer.MAX_VALUE, 100));
p1.setBackground(Color.GREEN);
frame.add(p1, BorderLayout.NORTH);
JPanel p2 = new JPanel();
p2.setPreferredSize(new Dimension(Integer.MAX_VALUE, 100));
p2.setBackground(Color.GREEN);
frame.add(p2, BorderLayout.SOUTH);
JPanel p3 = new JPanel();
p3.setPreferredSize(new Dimension(100, Integer.MAX_VALUE));
p3.setBackground(Color.GREEN);
frame.add(p3, BorderLayout.WEST);
JPanel p4 = new JPanel();
p4.setPreferredSize(new Dimension(200, Integer.MAX_VALUE));
p4.setBackground(Color.GREEN);
frame.add(p4, BorderLayout.EAST);
PhotoComponent pc = new PhotoComponent(new Dimension(500, 300));
pc.setFocusable(true);
JScrollPane sp = new JScrollPane(pc);
sp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
sp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
sp.getViewport().setBackground(Color.CYAN);
frame.add(sp, BorderLayout.CENTER);
final JColorChooser colorChooser = new JColorChooser();
colorChooser.getSelectionModel().addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
Color color = colorChooser.getColor();
if (color != null)
pc.setColor(color);
}
});
p4.add(colorChooser);
frame.setFocusable(true);
frame.setPreferredSize(new Dimension(900, 600));
frame.pack();
frame.setVisible(true);
frame.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
pc.keyListener.keyTyped(e);
}
});
}
}
My JComponent :
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
public class PhotoComponent extends JComponent {
Point position = null;
ArrayList<Text> textList = new ArrayList<>();
Text currentText = null;
Color color = Color.BLACK;
KeyAdapter keyListener;
public PhotoComponent(Dimension dimension){
this.setFocusable(true);
this.setVisible(true);
this.setPreferredSize(dimension);
addListeners();
}
void addListeners(){
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
currentText = new Text(e.getPoint());
textList.add(currentText);
}
});
this.keyListener = new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
super.keyTyped(e);
if(currentText != null){
currentText.addCharacter(e.getKeyChar());
repaint();
}
}
};
this.addKeyListener(this.keyListener);
}
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Color oldColor = g2.getColor();
g2.setColor(Color.WHITE);
g2.fill(this.getBounds());
g2.setColor(this.color);
for(Text t : textList){
t.draw(g2);
}
g2.setColor(oldColor);
}
public void setColor(Color color) {
this.color = color;
}
}
And my Text object
import java.awt.*;
public class Text {
Point position = null;
String content = "";
public Text(Point position){
this.position = position;
}
public void addCharacter(char newCharacter){
this.content = newCharacter;
}
public void draw(Graphics2D g2){
g2.drawString(content, position.x, position.y);
}
}
The little manipulation with the KeyListener in the frame is because the JComponent doesn't respond to KeyEvents. I hope I made my probleme clears and thanks in advance
CodePudding user response:
It's because your component has lost focus so it will no longer receive mouse events. I changed your mouse listener to include a requestFocusInWindow
which is the JComponent method. The focus will go back to your PhotoComponent and you can type again after clicking on the panel.
this.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
currentText = new Text(e.getPoint());
textList.add(currentText);
requestFocusInWindow();
}
});
CodePudding user response:
The little manipulation with the KeyListener in the frame is because the JComponent doesn't respond to KeyEvents.
This is not needed. Don't add a KeyListener to the frame.
Only add the KeyListener to the PhotoComponent.
A KeyEvent is only dispatched to the component that has focus. So you want your PhotoComponent to have focus when the frame is made visible.
You do this by adding:
/*
frame.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
pc.keyListener.keyTyped(e);
}
});
*/
pc.requestFocusInWindow();
I don't understand why it didn't work from the changeListener though
Sometimes Swing components add code to the EDT so code is not executed in the order you expect. So the PhotoComponent might get focus but the color chooser may grab it back.
So in your ChangeListener you first try:
pc.requestFocusInWindow();
In this case it doesn't work.
So the logic in your change listener should be:
public void stateChanged(ChangeEvent e) {
Color color = colorChooser.getColor();
if (color != null)
pc.setColor(color);
SwingUtilities.invokeLater(() -> pc.requestFocusInWindow());
}
Which will make sure the code is executed after all the color chooser logic is finished executing.
Also you setColor() method should be:
this.color = color;
repaint();
A Swing components should be responsible for repainting itself when a property of the component is changed, the same way you repaint the component when the text is changed.
Also when writing a listener, then is no need to invoke super()
on the event. The default implementation of the adapter classes do nothing.