Overlay Swing Text Label as Text is Entered

假如想象 提交于 2019-12-28 04:36:08

问题


Various forms in my program use JTables for which I've been able to use a Key Listener to select a row as the user types. This works great but I would like to provide some feedback to the user to show what text is being entered.

I've tried creating frame/labels but can't get them to show correctly.

My basic thoughts were - create the frame (if it doesn't already exist), create the label and set the text. Eg:

private void showSearchLabel(String search) {
    if (null == searchTextFrame) {
        searchTextFrame = new JFrame("searchTextFrame");
        searchTextFrame.setBackground(new Color(0, 0, 0, 0));
        searchTextFrame.setUndecorated(true);
        searchTextFrame.setAlwaysOnTop(true);

        searchTextFrame.getContentPane().setLayout(new java.awt.BorderLayout());
        searchTextLabel = new JLabel();
        searchTextFrame.getContentPane().add(searchTextLabel);
        searchTextFrame.pack();
        searchTextFrame.setVisible(true);
    }

    searchTextLabel.setText(search);

}

showSearchLabel is called by a Key Listener which adds the most recent key press to the search string. Backspace clears the string (and removes the frame/label). Enter key selects the item in the table and should also remove the frame/label.

What am I missing?

EDIT: Clarification - using the code above, nothing shows at all.

If I set the text when creating the label, the first character is visible (which is to be expected as at that point, the user has only typed one character). Calling .setText(search) after this point, the text is not updated. Note - this is visible in the very top/left hand corner of the screen, which is not really where I want it (ideally, would like it to show within the JTable).


回答1:


I've had to repeat something like this a number of times over the last few months, using a verity of different UI approaches (hiding a field till the user typed and then making it visible via a BorderLayout; a static field on the screen; etc...) and was attempting to build a more concise and re-usable library mechanism which would allow me to configure any JTable with easy filterable support.

One of the reasons I use a JTextField over a JLabel, is it handles user input better then anything you or I might be able to produce (in a reasonable amount of time), but that's me ;)

Personally, I prefer not to open any new windows, as this can introduce other issues, like focus related issues as well as the window been hidden behind the current window on some platforms, etc.

Equally, the few times I've "unhidden" a field, the layout is forced to change, which can look ugly and there isn't always a need to have the filter field visible all the time. Instead, what I was trying to achieve was a "popup" field which appeared inline with with table itself, but would be capable of been made visible anywhere within the viewable area of the parent JScrollPane...

Now, this approach may not suit your needs, but it's what I've been work towards...

Again, the core concept was to provide functionality that didn't require me to extend a JTable, but could wrapped around an existing JTable. The core functionality is provided by a utility class and is managed through two interfaces which provide externalized control over some of the core functionality (how the filter is applied to the JTable and how a cancel operation works). The utility class also provides the means to configure what keyboard action will cancel the filter field

The JTextField which is displayed, is actually added to the JTable itself. I had thought about trying to force a "offset" into the view, so all the rows were pushed down, but the only way I know how to do this is to extend the JTable itself, which is what I was trying to avoid

public class TableUtilities {

    private static WeakHashMap<JTable, FilterSupport> mapFilters = new WeakHashMap(25);

    public static void installFilterSupport(JTable table, IFilterListener listener, KeyStroke escapeKey) {
        FilterSupport support = new FilterSupport(table, listener, escapeKey);
        mapFilters.put(table, support);
    }

    public static void uninstallFilterSupport(JTable table) {
        FilterSupport support = mapFilters.remove(table);
        if (support != null) {
            support.uninstall();
        }
    }

    protected static class FilterSupport implements IFilterSupport {

        private JViewport viewport;
        private JTable table;
        private JTextField searchField;
        private Timer filterTimer;

        private HierarchyListener hierarchyListener;
        private ChangeListener changeListener;
        private IFilterListener filterListener;

        public FilterSupport(JTable table, IFilterListener listener, KeyStroke escapeKey) {
            this.table = table;
            this.filterListener = listener;
            table.setFillsViewportHeight(true);

            hierarchyListener = new HierarchyListener() {
                @Override
                public void hierarchyChanged(HierarchyEvent e) {
                    long flags = e.getChangeFlags();
                    if ((flags & HierarchyEvent.PARENT_CHANGED) != 0) {
                        if (e.getChanged().equals(table)) {
                            JTable table = (JTable) e.getChanged();
                            if (e.getChangedParent() instanceof JViewport) {
                                if (table.getParent() == null) {
                                    uninstall();
                                } else {
                                    install();
                                }
                            }
                        }
                    }
                }
            };
            changeListener = new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    JViewport viewport = (JViewport) e.getSource();
                    Rectangle viewRect = viewport.getViewRect();
                    searchField.setSize(searchField.getPreferredSize());
                    int x = (viewRect.x + viewRect.width) - searchField.getWidth();
                    int y = viewRect.y;
                    searchField.setLocation(x, y);
                }
            };

            table.addHierarchyListener(hierarchyListener);

            searchField = new JTextField(20);
            searchField.setVisible(false);
            searchField.getDocument().addDocumentListener(new DocumentListener() {

                @Override
                public void insertUpdate(DocumentEvent e) {
                    filterChanged();
                }

                @Override
                public void removeUpdate(DocumentEvent e) {
                    filterChanged();
                }

                @Override
                public void changedUpdate(DocumentEvent e) {
                    filterChanged();
                }
            });

            searchField.addFocusListener(new FocusAdapter() {
                @Override
                public void focusLost(FocusEvent e) {
                    cancelField();
                }
            });

            filterTimer = new Timer(250, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    applyFilter();
                }
            });
            filterTimer.setRepeats(false);

            table.addKeyListener(new KeyAdapter() {

                @Override
                public void keyTyped(KeyEvent e) {
                    if (Character.isLetterOrDigit(e.getKeyChar())) {
                        searchField.setVisible(true);
                        table.revalidate();
                        table.repaint();
                        // ?? Should this maintain the current filter value?
                        searchField.setText(null);
                        searchField.requestFocusInWindow();
                        SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                searchField.dispatchEvent(e);
                            }
                        });

                    }
                }

            });

            Action escapeAction = new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    cancelField();
                }
            };

            bindKeyStrokeTo(table, JComponent.WHEN_FOCUSED, "clear", escapeKey, escapeAction);
            bindKeyStrokeTo(searchField, JComponent.WHEN_FOCUSED, "clear", escapeKey, escapeAction);

        }

        protected void cancelField() {
            searchField.setVisible(false);
            table.requestFocusInWindow();
            table.revalidate();
            table.repaint();
            if (filterListener != null) {
                filterListener.filterCancelled(table, this);
            }
        }

        public void filterChanged() {
            filterTimer.restart();
        }

        protected void applyFilter() {

            if (filterListener != null) {
                filterListener.filterChanged(table, searchField.getText());
            }

        }

        protected void uninstall() {
            filterTimer.stop();
            if (viewport != null) {
                if (changeListener != null) {
                    viewport.removeChangeListener(changeListener);
                }
                table.remove(searchField);
                searchField.setVisible(false);
            }
            viewport = null;
        }

        protected void install() {
            if (viewport != null) {
                uninstall();
            }
            Container parent = table.getParent();
            if (parent instanceof JViewport) {
                viewport = (JViewport) parent;
                viewport.addChangeListener(changeListener);
                table.add(searchField);
            }
        }

        @Override
        public String getFilter() {
            return searchField.getText();
        }

        @Override
        public void setFilter(String filter) {
            searchField.setText(filter);
        }

    }

    public static void bindKeyStrokeTo(JComponent parent, int condition, String name, KeyStroke keyStroke, Action action) {
        InputMap im = parent.getInputMap(condition);
        ActionMap am = parent.getActionMap();

        im.put(keyStroke, name);
        am.put(name, action);
    }

    public static interface IFilterSupport {

        public String getFilter();

        public void setFilter(String filter);

    }

    public static interface IFilterListener {

        public void filterChanged(JTable table, String filter);

        public void filterCancelled(JTable table, IFilterSupport support);
    }

}

And my test class...

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.WeakHashMap;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.RowFilter;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableRowSorter;

public class TestSearchTable {

    public static void main(String[] args) {
        new TestSearchTable();
    }

    public TestSearchTable() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new BorderLayout());

            DefaultTableModel model = new DefaultTableModel(
                            new Object[][]{
                                {"Tiana", "Wilmer"},
                                {"Twana", "Wingate"},
                                {"Cody", "Baumgarten"},
                                {"Venus", "Espy"},
                                {"Savanna", "Buckmaster"},
                                {"Adrien", "Edgecomb"},
                                {"Lauretta", "Sassman"},
                                {"Vivienne", "Glasco"},
                                {"Cassy", "Merryman"},
                                {"Mitchel", "Jarvie"},
                                {"Kelsi", "Casebeer"},
                                {"Rosy", "Rizzi"},
                                {"Bernice", "Capote"},
                                {"Tijuana", "Launius"},
                                {"Jeffie", "Crownover"},
                                {"Selena", "Leavy"},
                                {"Damon", "Tulloch"},
                                {"Norris", "Devitt"},
                                {"Cecil", "Burgio"},
                                {"Queen", "Mechling"}},
                            new Object[]{"First Name", "Last name"}
            ) {

                @Override
                public boolean isCellEditable(int row, int column) {
                    return false;
                }

            };

            JTable table = new JTable(model);
            table.setAutoCreateRowSorter(true);
            TableUtilities.installFilterSupport(table,
                            new TableUtilities.IFilterListener() {
                                @Override
                                public void filterChanged(JTable table, String filter) {
                                    TableRowSorter sorter = (TableRowSorter) table.getRowSorter();
                                    if (filter == null || filter.trim().length() == 0) {
                                        filter = "*";
                                    }

                                    if (!filter.startsWith("*") || !filter.endsWith("*")) {
                                        filter = "*" + filter + "*";
                                    }
                                    filter = wildcardToRegex(filter);
                                    filter = "(?i)" + filter;
                                    sorter.setRowFilter(RowFilter.regexFilter(filter));
                                }

                                @Override
                                public void filterCancelled(JTable table, TableUtilities.IFilterSupport support) {
//                                  support.setFilter(null);
                                }
                            },
                            KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
            add(new JScrollPane(table));
        }

    }

    public static String wildcardToRegex(String wildcard) {

        StringBuilder s = new StringBuilder(wildcard.length());
        s.append('^');

        for (int i = 0, is = wildcard.length(); i < is; i++) {

            char c = wildcard.charAt(i);
            switch (c) {
                case '*':
                    s.append(".*");
                    break;
                case '?':
                    s.append(".");
                    break;
                // escape special regexp-characters
                case '(':
                case ')':
                case '[':
                case ']':
                case '$':
                case '^':
                case '.':
                case '{':
                case '}':
                case '|':
                case '\\':
                    s.append("\\");
                    s.append(c);
                    break;
                default:
                    s.append(c);
                    break;
            }

        }

        s.append('$');
        return (s.toString());

    }

}



回答2:


A new JLabel() has "an empty string for the title." It's preferred size is zero. You can

  • Add the required space in the constructor.

    searchTextLabel = new JLabel("        ");
    
  • Invoke pack() on the enclosing frame, after the call to setVisible().

    f.setVisible(true);
    …
    searchTextFrame.pack();
    
  • Add a single space in the constructor, to establish the height, and invoke validate() on the enclosing Container.

    searchTextLabel = new JLabel(" ");
    …
    f.setVisible(true);
    …
    searchTextLabel.setText(search);
    searchTextLabel.validate();
    


来源:https://stackoverflow.com/questions/29500713/overlay-swing-text-label-as-text-is-entered

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!