jTextArea stops showing highlighting on text after losing focus - java

When my jTextArea is in focus it allows text highlighting, but it doesn't show the text selection when it loses focus. Is it possible to continue displaying the text highlighting even if the user moves focus to another component on the related jFrame?

One simple workaround for caret selection is a simple subclassing of DefaultCaret:
textArea.setCaret(new DefaultCaret() {
#Override
public void setSelectionVisible(boolean visible) {
super.setSelectionVisible(true);
}
});

but doesn't show selection on text when looses focus.
there are three ways:
use JTextPane, see Oracle tutorial
easiest in the case that we talking about selection as painting artefact from Mouse Event see my question How to override DefaultCaret#setBlinkRate(), great knowledge and answer by #camickr
or programatically override Highlighter
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
public class MultiHighlight implements ActionListener {
private JTextComponent comp;
private String charsToHighlight;
public MultiHighlight(JTextComponent c, String chars) {
comp = c;
charsToHighlight = chars;
}
#Override
public void actionPerformed(ActionEvent e) {
Highlighter h = comp.getHighlighter();
h.removeAllHighlights();
String text = comp.getText().toUpperCase();
for (int j = 0; j < text.length(); j += 1) {
char ch = text.charAt(j);
if (charsToHighlight.indexOf(ch) >= 0) {
try {
h.addHighlight(j, j + 1, DefaultHighlighter.DefaultPainter);
} catch (BadLocationException ble) {
}
}
}
}
public static void main(String args[]) {
final JFrame frame = new JFrame("MultiHighlight");
frame.add(new JTextField("Another focusable JComponents"), BorderLayout.NORTH);
JTextArea area = new JTextArea(10, 20);
area.setText("This is the story\nof the hare who\nlost his spectacles."
+ "\nThis is the story\nof the hare who\nlost his spectacles.");
frame.getContentPane().add(new JScrollPane(area), BorderLayout.CENTER);
JButton b = new JButton("Highlight All Vowels");
b.addActionListener(new MultiHighlight(area, "aeiouAEIOU"));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(b, BorderLayout.SOUTH);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
frame.pack();
frame.setVisible(true);
}
});
}
}

Related

Java Swing: "permanent tooltip" hint that follows mouse cursor on App's JFrame

I would like to create a small tooltip-like (hint) window that will popup upon request then follow the mouse cursor anywhere on the application's JFrame until it is later destroyed. It really is a global hint message asking the user to perform a specific task (between many different windows) and once they have done so, it will disappear. For example:
GlobalToolTip panelHint = new GlobalToolTip("Global hint message.");
panelHint.show(); //Will remain visible and follow mouse
... //Waiting for user to perform a specific action or cancel
panelHint.hide(); //Hidden/destroyed here
I'm looking to either create my own class extending from JToolTip/JPanel or to override the default tooltip behavior if possible. Perhaps using the app JFrame's JLayeredPane or glassPane?
I've checked a handful of other solutions but all apply to a single component's behavior only. I also tried having a JPanel on the glassPane but it wasn't being drawn properly over some of the other window contents. Perhaps I was not calling its draw methods at the correct place?
This seems like it should be a pretty easy problem to solve, but I have not been able to. Any help would be appreciated.
Personally, I wouldn’t bother with the glassPane and I wouldn’t bother with trying to make a JToolTip or the ToolTipManager do things they weren’t meant to do. I would just make a JLabel that mimics a JToolTip:
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.MouseEvent;
import java.awt.event.AWTEventListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToggleButton;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class FollowTip {
private JWindow tip;
private final AWTEventListener mouseHandler = e -> {
Window window = tip.getOwner();
MouseEvent event = null;
switch (e.getID()) {
case MouseEvent.MOUSE_ENTERED:
case MouseEvent.MOUSE_MOVED:
case MouseEvent.MOUSE_DRAGGED:
event = (MouseEvent) e;
if (window.isAncestorOf(event.getComponent())) {
Point loc = event.getLocationOnScreen();
tip.setLocation(loc.x + 10, loc.y + 10);
tip.setVisible(true);
}
break;
case MouseEvent.MOUSE_EXITED:
event = (MouseEvent) e;
Point p = SwingUtilities.convertPoint(
event.getComponent(), event.getPoint(), window);
if (!window.contains(p)) {
tip.setVisible(false);
}
break;
default:
break;
}
};
public FollowTip(String text,
Window window) {
JLabel tipLabel = new JLabel(text);
tipLabel.setForeground(UIManager.getColor("ToolTip.foreground"));
tipLabel.setBackground(UIManager.getColor("ToolTip.background"));
tipLabel.setFont(UIManager.getFont("ToolTip.font"));
tipLabel.setBorder(UIManager.getBorder("ToolTip.border"));
tip = new JWindow(window);
tip.setType(Window.Type.POPUP);
tip.setFocusableWindowState(false);
tip.getContentPane().add(tipLabel);
tip.pack();
}
public void activate() {
Window window = tip.getOwner();
window.getToolkit().addAWTEventListener(mouseHandler,
AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
Point p = window.getMousePosition();
if (p != null) {
SwingUtilities.convertPointToScreen(p, window);
tip.setLocation(p.x + 10, p.y + 10);
tip.setVisible(true);
}
}
public void deactivate() {
Window window = tip.getOwner();
window.getToolkit().removeAWTEventListener(mouseHandler);
tip.setVisible(false);
}
static void showWindow() {
Object[][] data = new Object[12][];
Object[] headings = new Object[data.length];
for (int i = 0; i < data.length; i++) {
data[i] = new Object[data.length];
for (int j = 0; j < data[i].length; j++) {
data[i][j] = (i + 1) * (j + 1);
}
headings[i] = i;
}
JTable table = new JTable(data, headings);
JToggleButton button = new JToggleButton("Active");
JPanel buttonPanel = new JPanel();
buttonPanel.add(button);
JFrame frame = new JFrame("FollowTip");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new JScrollPane(table));
frame.getContentPane().add(buttonPanel, BorderLayout.PAGE_END);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
FollowTip tip = new FollowTip("This is a tip", frame);
button.addActionListener(e -> {
if (button.isSelected()) {
tip.activate();
} else {
tip.deactivate();
}
});
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> showWindow());
}
}

FocusListener & JOptionPane

There is the code of my simple Program.
There are four textFields.
when cursor is on first textField JOptionPane is Created and when I press ok
cursor moves to next field and OptionPane is created again
and so on
when cursor is on fourth field and I click OK on OptionPane,cursor moves to fifth field "f".
when cursor is in field,I print the possition of the field in array: System.out.println("first or Second or Third or Fourth")
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class Hello extends JFrame implements ActionListener, FocusListener {
public JTextField[] fields = new JTextField[4];
public JPanel panel = new JPanel();
public JTextField f = new JTextField(12);
public static void main(String[] args) {
new Hello();
}
public Hello() {
for (int i = 0; i < 4; i++) {
fields[i] = new JTextField(12);
fields[i].addFocusListener(this);
panel.add(fields[i]);
}
add(panel);
add(f);
setTitle("Hello World");
getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.PAGE_AXIS));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(920, 420);
setLocation(100, 100);
setVisible(true);
}
#Override
public void actionPerformed(ActionEvent ae) {
}
#Override
public void focusGained(FocusEvent fe) {
if (fe.getSource() == fields[0]) {
JOptionPane.showMessageDialog(null, "HELLO");
fields[1].requestFocus();
System.out.println("FIRST");
} else if (fe.getSource() == fields[1]) {
JOptionPane.showMessageDialog(null, "HELLO");
fields[2].requestFocus();
System.out.println("SECOND");
} else if (fe.getSource() == fields[2]) {
JOptionPane.showMessageDialog(null, "HELLO");
fields[3].requestFocus();
System.out.println("THIRD");
} else if (fe.getSource() == fields[3]) {
JOptionPane.showMessageDialog(null, "HELLO");
f.requestFocus();
System.out.println("FOURTH")
}
}
#Override
public void focusLost(FocusEvent fe) {
}
}
When there is no OptionPane,the cursor moves forward from first field to the fourth and prints:
FIRST
SECOND
THIRD
FOURTH
but when there is JOptionPane
the output is :
FIRST
SECOND
FIRST
SECOND
THIRD
SECOND
THIRD
FOURTH
THIRD
FOURTH
FOURTH
One can see that after second field it comes back to first,
after third field it comes back to second,instead of to go to fourth
after fourth field it comes back to third.
I want to know why? and how can I fix this
The problem is that every time you click OK on the JOptionPane, the focus is returned to the last JTextField active before the JOptionPane was shown, so a new requestFocus event is added to the event queue for that control. Actually after the first time you click OK while executing your code, several dialogs are fire, you just don't see it because you show the same text (HELLO) every time. I have changed your code to make it work. Hope it helps!
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.ArrayList;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class testOptionPane extends JFrame implements ActionListener, FocusListener {
public ArrayList<JTextField> fields = new ArrayList<>();
public JPanel panel = new JPanel();
public JTextField f = new JTextField(12);
private int currentField = 0;
private boolean focusReturned = false;
public static void main(String[] args) {
new testOptionPane();
}
public testOptionPane() {
for (int i = 0; i < 4; i++) {
JTextField tf = new JTextField(12);
fields.add(tf);
tf.addFocusListener(this);
panel.add(tf);
}
add(panel);
fields.add(f);
add(f);
setTitle("Hello World");
getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.PAGE_AXIS));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(920, 420);
setLocation(100, 100);
setVisible(true);
}
#Override
public void actionPerformed(ActionEvent ae) {
}
#Override
public void focusGained(FocusEvent fe) {
if (fe.getSource() == fields.get(currentField)) {
if (!focusReturned) {
JOptionPane.showMessageDialog(this, "focus on field " + String.valueOf(currentField));
System.out.println(currentField);
focusReturned = true;
} else {
currentField++;
focusReturned = false;
if (currentField < fields.size()) {
fields.get(currentField).requestFocus();
}
}
}
}
#Override
public void focusLost(FocusEvent fe) {
}
}

Select text in 2 JTextarea at the same time

I am trying to do a small app that compares two similar texts contained in 2 JTextarea. I am wondering if it's possible to select text from the first JTextarea and automatically select the text on the second JTeaxtarea (lets consider that it's guarantee that the 2 JTextarea will have the same text for now) ?
Should I share events or listeners ?
Thank you
This would be so much easier if JTextComponent supported a selection model...
Basically, what you can do is attach a ChangeListener to the JTextArea's Caret and monitor for changes to the Caret, changing the selection of the other JTextArea in response...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
public class MirrorTextSelection {
public static void main(String[] args) {
new MirrorTextSelection();
}
public MirrorTextSelection() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JTextArea left;
private JTextArea right;
private DefaultHighlighter.DefaultHighlightPainter highlightPainter;
public TestPane() {
highlightPainter = new DefaultHighlighter.DefaultHighlightPainter(UIManager.getColor("TextArea.selectionBackground"));
left = new JTextArea(20, 20);
left.setWrapStyleWord(true);
left.setLineWrap(true);
right = new JTextArea(20, 20);
right.setWrapStyleWord(true);
right.setLineWrap(true);
left.setText("I am trying to do a small app that compares two similar texts contained in 2 JTextarea. I am wondering if it's possible to select text from the first JTextarea and automatically select the text on the second JTeaxtarea (lets consider that it's guarantee that the 2 JTextarea will have the same text for now) ? Should I share events or listeners ? Thank you");
right.setText("I am trying to do a small app that compares two similar texts contained in 2 JTextarea. I am wondering if it's possible to select text from the first JTextarea and automatically select the text on the second JTeaxtarea (lets consider that it's guarantee that the 2 JTextarea will have the same text for now) ? Should I share events or listeners ? Thank you");
setLayout(new GridLayout(0, 2));
add(new JScrollPane(left));
add(new JScrollPane(right));
left.getCaret().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
int dot = left.getCaret().getDot();
int mark = left.getCaret().getMark();
right.setCaretPosition(mark);
right.moveCaretPosition(dot);
}
});
}
}
}
Now, when you run this, you will find that the right side doesn't seem to get highlighted...what?!
The selection is changing, it's just not been rendered because the component doesn't have focus...
Instead, you could use a Highlighter to highlight the text...
private DefaultHighlighter.DefaultHighlightPainter highlightPainter;
//...
highlightPainter = new DefaultHighlighter.DefaultHighlightPainter(UIManager.getColor("TextArea.selectionBackground"));
left.getCaret().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
int dot = left.getCaret().getDot();
int mark = left.getCaret().getMark();
right.getHighlighter().removeAllHighlights();
try {
int start = Math.min(dot, mark);
int end = Math.max(dot, mark);
right.getHighlighter().addHighlight(start, end, highlightPainter);
} catch (BadLocationException ex) {
ex.printStackTrace();
}
}
});
Okay, this is now working and you can control the background color of the highlight...
There is another alternative...We can replace the Caret of the right JTextArea with one that doesn't hide the selection when focus is lost...
public class HighlightCaret extends DefaultCaret {
#Override
public void install(JTextComponent c) {
super.install(c);
setSelectionVisible(true);
}
#Override
public void focusGained(FocusEvent e) {
JTextComponent component = getComponent();
if (component.isEnabled()) {
if (component.isEditable()) {
setVisible(true);
}
setSelectionVisible(true);
}
}
#Override
public void focusLost(FocusEvent e) {
setVisible(false);
}
}
Then we set the Caret to right...
right.setCaret(nwe HighlightCaret());
This means we don't need the Highlighter code, we can stick with the original and we get control over not only the background selection color but also the foreground selection color as well...

How to remove a JButton in a JButton matrix?

I want to remove a certain botton using MouseListener from a matrix of bottons and add a JLabel in the empty, so I use:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public MyClass(){
object = new Object();
bottons = new JButton[5][5];
labels = new JLabel[5][5];
myPanel = new JPanel();
myPanel.setLayout(new GridLayout(5,5));
click =false;
for(int i = 0 ; i<5 ; i++){
for(int j = 0; j<5 ; j++){
myPanel.add(bottons[i][j] = new JButton());
}
}
}
public void mouseReleased(MouseEvent e)
if(click){
remove(bottons[object.getx][object.gety]);//this is correct? or is there another way?
myJPanel.add(labels[object.getx][object.gety] = new JLabel("1"));
click = false;
}
But nothing happen, haha
Thanks for the help.
When you add/remove components from a visible GUI the basic code is:
panel.remove(...);
panel.add(...);
panel.revalidate();
panel.repaint();
Also "MyJPanel" is not a standard Java variable name. Variable names in Java should NOT start with an upper case character. You didn't do that with your other variables, so be consistent!
Given the fact the i and j have no context in the mouse listener method, no, it's probably not a good idea.
The next question is, what is the mouse listener attached to? If it's attached to the button, then it might be better to use a ActionListener.
In either case you could use the source of the event...
Object source = e.getSource();
if (source instanceof JButton) {
JButton btn = (JButton)source;
//find index of button in array...
remove(btn);
//...
revalidate();
}
Updated
A simpler solution might be to simply dump the buttons and labels into a List, for example...
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ButtonUpdates {
public static void main(String[] args) {
new ButtonUpdates();
}
public ButtonUpdates() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel implements ActionListener {
private List<JButton> btns = new ArrayList<>(25);
private List<JLabel> lbls = new ArrayList<>(25);
public TestPane() {
setLayout(new GridLayout(5,5));
for (int index = 0; index < 25; index++) {
JButton btn = new JButton(Integer.toString(index));
JLabel lbl = new JLabel(Integer.toString(index));
btns.add(btn);
lbls.add(lbl);
btn.addActionListener(this);
add(btn);
}
}
#Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source instanceof JButton) {
JButton btn = (JButton) source;
int index = btns.indexOf(source);
JLabel lbl = lbls.get(index);
index = getComponentZOrder(btn);
remove(btn);
add(lbl, index);
revalidate();
}
}
}
}
This makes looking up what has being actioned and what needs to be replaced easier.
When switching components, you also need to know where the component needs to be added, for this I simply used getComponentZOrder before I removed the JButton

How to set number of selectable JRadioButton

How would I go about setting the number of selectable items of JRadioButtons?
I tried adding the radiobuttons to a buttongroup, and overriding the buttongroup class, but cant figure which method to modify.
Basically, I want to allow selection of only two radiobuttons. I am aware this is possible using checkboxes, but I need the "roudness" of the radiobuttons, and figure this should be an easier way to go, instead of modifying the look and feel of the checkbox.
Thanks a bunch! :)
Here is an example:
package com.haraj.test.java;
import java.awt.GridLayout;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.LinkedList;
import java.util.Queue;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.SwingUtilities;
public class JRadioButtonTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel contentPane = (JPanel) frame.getContentPane();
contentPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new GridLayout());
final Queue<JRadioButton> selectedButtons = new LinkedList<JRadioButton>();
ItemListener listener = new ItemListener()
{
#Override
public void itemStateChanged(ItemEvent e)
{
JRadioButton newButton = (JRadioButton) e.getSource();
if(e.getStateChange() == ItemEvent.DESELECTED) selectedButtons.remove(newButton);
else
{
if(selectedButtons.size() == 2)
{
JRadioButton oldButton = selectedButtons.poll();
if(oldButton != newButton) oldButton.setSelected(false);
}
selectedButtons.add(newButton);
}
}
};
JRadioButton[] buttons = new JRadioButton[6];
for(int i = 0; i < buttons.length; i++)
{
buttons[i] = new JRadioButton();
buttons[i].addItemListener(listener);
contentPane.add(buttons[i]);
}
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
});
}
}
One way would be to add an ActionListener to each individual radiobutton which updates a counter if the button is selected.
You can read about jRadioButton functions HERE.
You can then do a function if the counter hits two which makes the other buttons grey (unclickable) using:
.setActionCommand("disable");
You can find more info about the possible methods in the API.

Categories

Resources