0

I am trying to implement buttons that search a specific word in JTextArea in my Java Swing application. Can anyone help me to implement buttons to highlight the next and previous match ? Please review my code for searchbutton aswell. For now it works fine.

PS. I want run searchButton in new Thread. Is it okay like that ???

searchButton.addActionListener(e -> new Thread(() -> {
        int pos = 0;
        // Get the text to find...convert it to lower case for eaiser comparision
        String find = searchField.getText().toLowerCase();
        // Focus the text area, otherwise the highlighting won't show up
        textArea.requestFocusInWindow();
        // Make sure we have a valid search term
        if (find != null && find.length() > 0) {
            Document document = textArea.getDocument();
            int findLength = find.length();
            try {
                boolean found = false;
                // Rest the search position if we're at the end of the document
                if (pos + findLength > document.getLength()) {
                    pos = 0;
                }
                // While we haven't reached the end...
                // "<=" Correction
                while (pos + findLength <= document.getLength()) {
                    // Extract the text from the docuemnt
                    String match = document.getText(pos, findLength).toLowerCase();
                    // Check to see if it matches or request
                    if (match.equals(find)) {
                        found = true;
                        break;
                    }
                    pos++;
                }
                // Did we find something...
                if (found) {
                    // Get the rectangle of the where the text would be visible...
                    Rectangle viewRect = textArea.modelToView(pos);
                    // Scroll to make the rectangle visible
                    textArea.scrollRectToVisible(viewRect);
                    // Highlight the text
                    textArea.setCaretPosition(pos + findLength);
                    textArea.select(pos, pos + findLength);
                    textArea.grabFocus();

                }

            } catch (Exception exp) {
                exp.printStackTrace();
            }
        }
    }));

2 Answers2

1

There are two ways to search through the text for matches in your JTextArea:

  1. Use String.indexOf(idx) [forward] and String.lastIndexOf(idx) [backward] on the document text, where idx is the current caret location. You may also want to use String.toUppercase / toLowercase on both the search text and the document text if you don't want your searching to be case sensitive.

  2. Use Matcher/Pattern for regular expression searching. This is the more powerful but complicated solution. And just FYI, I am working on rolling out a DHTML solution and for the longest time I wanted to include regex searching. But in the end I didn't - I don't think customers (unless they are internal maybe) care about learn how to write regexes

ControlAltDel
  • 33,923
  • 10
  • 53
  • 80
0
  1. Don't use a Thread. You only use a Thread for time consuming tasks. Searching a text string is not a time consuming task. Also, updates to Swing components need to be done on the EDT. So you want the highlighting to be done on the EDT.

  2. Don't use grabFocus(). You have already used requestFocusInWindow() which is the correct method to use.

  3. Use the String.indexOf(…) method (as suggested by @ControlAltDel). Then there is no need for looping code. You just search for the text from the current caret position and you either find the word or you don't.

For what its worth I happen to have old code lying around that does this:

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;

public class TextComponentFindNext extends JFrame implements ActionListener
{
    JTextComponent textComponent;
    JTextField textField;

    public TextComponentFindNext()
        throws Exception
    {
        textComponent = new JTextPane();
        JScrollPane scrollPane = new JScrollPane( textComponent );
        scrollPane.setPreferredSize( new Dimension(500, 400) );
        getContentPane().add(scrollPane, BorderLayout.NORTH);

        textField = new JTextField(10);
        textField.setText("im");
        textField.addActionListener( this );
        getContentPane().add(textField, BorderLayout.WEST);

        JButton button = new JButton("Find Next");
        button.addActionListener( this );
        getContentPane().add(button, BorderLayout.EAST);

        FileReader reader = new FileReader( "TextComponentFindNext.java" );
        BufferedReader br = new BufferedReader(reader);
        textComponent.read(br, null);
        br.close();
    }

    public void actionPerformed(ActionEvent e)
    {
        String searchString = "";

        //  this works
        try
        {
            Document doc = textComponent.getDocument();
            searchString = doc.getText(0, doc.getLength());
        }
        catch(BadLocationException ble) {}

        //  this doesn't work
//      searchString = textComponent.getText();

        int offset = textComponent.getCaretPosition();
        String searchText = textField.getText();

        int start = searchString.indexOf(searchText, offset);

        if (start != -1)
        {
            textComponent.select(start, start + searchText.length());
            textComponent.requestFocusInWindow();
        }
    }

    public static void main(String[] args)
        throws Exception
    {
        JFrame frame = new TextComponentFindNext();
        frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible(true);
    }
}

There is no need for the scroll logic, it will scroll automatically when the text is selected.

If you want to get fancy with scrolling you can "center" the line the contains the text. Check out Text Utilities for some helper methods that allow you to do this.

Note, it is better to get the text from the Document, instead of the components. The text from the Document only contains a "\n" string for the end of line string. This means it will work for both a JTextArea and a JTextPane. See: Text and New Lines for more information.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • Thank you for your answer ! Im getting right to it now :) I understand your point about the thread but its just for educational reasons :) – Maciek Łapiński Apr 23 '20 at 11:20
  • *but its just for educational reasons* - that was my point. It is wrong to use the Thread. If you want to use a Thread, then use it when it is appropriate. Don't forget to "accept" the answer, by clicking on the checkmark, so people know the problem has been solved. – camickr Apr 23 '20 at 15:13
  • yeah, thanks :) Once I solve my problem Ill click that. Have a good one ;) – Maciek Łapiński Apr 23 '20 at 17:06