Temporarily disable new menus to prevent accidental clicks ...
MyMenuListener - How to use it:
fileMenu.addMenuListener(new MyMenuListener(fileMenu) { @Override protected void quickMenu(MyMenuListener.MyMenuWorker menuWorker) { menuWorker.add(new JMenuItem("Fast New Menu A")); menuWorker.add(new JMenuItem("Fast New Menu B")); } @Override protected void slowMenu(MyMenuListener.MyMenuWorker menuWorker) { /** do something here that takes a long time. */ menuWorker.add(new JMenuItem("Delayed Menu 1")); menuWorker.add(new JMenuItem("Delayed Menu 2")); } });
MyMenuListener - The code: (as a single executable java file)
import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.Timer; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; /** * Demonstrates how to add menu items to a menu, dynamically on a background * thread using a SwingWorker, after the menu has already been displayed on * screen. * * @author javajon.blogspot.com */ public class SwingWorkerMenuBar extends javax.swing.JFrame { private javax.swing.JMenu fileMenu; /** * Creates new form SwingWorkerMenuBar. (yay?) */ public SwingWorkerMenuBar() { initComponents(); /** * this part is required for the menu to be dynamic. */ fileMenu.addMenuListener(new MyMenuListener(fileMenu) { /** * This is the part that catches the menu just before it's displayed * so we can make immediate modifications. Any modifications you can * make from memory (i.e. without hitting a database or a file * system) can be made here and will display with the menu the first * time it becomes visible. Caution: if you put your * long-running code here then the menu won't show up at all until * your long-running code is done. */ @Override protected void quickMenu(MyMenuListener.MyMenuWorker menuWorker) { menuWorker.add(new JMenuItem("Fast New Menu A")); menuWorker.add(new JMenuItem("Fast New Menu B")); } /** * this is where we add menus that might take some time to build ie * data has to be looked up in the database or read from a file * store or network share. */ @Override protected void slowMenu(MyMenuListener.MyMenuWorker menuWorker) { for (int i = 500; i >= 50; i = (i / 33) * 25) { try { // simulate processing & send a new menu to UI Thread.sleep(i); menuWorker.add(new JMenuItem("New Menu " + i + " Delay")); } catch (InterruptedException ex) { Logger.getLogger(SwingWorkerMenuBar.class.getName()).log(Level.SEVERE, null, ex); } } } }); } public static abstract class MyMenuListener implements MenuListener, ActionListener { /** * This is the part that catches the menu just before it's displayed so * we can make immediate modifications. Any modifications you can make * from memory (i.e. without hitting a database or a file system) can be * made here and will display with the menu the first time it becomes * visible. Caution: if you put your long-running code here then * the menu won't show up at all until your long-running code is done. * * @param menuWorker use this object to add new menus */ protected abstract void quickMenu(MyMenuWorker menuWorker); /** * this is where we add menus that might take some time to build ie data * has to be looked up in the database or read from a file store or * network share. * * @param menuWorker use this object to add new menus */ protected abstract void slowMenu(MyMenuWorker menuWorker); /** * the menu with which this listener is associated. */ private final JMenu menu; /** * the menus that have been created using menuWorker.add(menuItem). */ private final ListmyMenus = new ArrayList (); /** * the menus that have been disabled to prevent the user from * accidentally clicking on a menu that wasn't there a moment ago. */ private final List menusToEnable = new ArrayList (); /** * basic lock object to make sure threads are playing nice with shared * memory. */ private final Object LOCK = new Object(); /** * this is the part that re-enables menu options. */ private final javax.swing.Timer timer = new Timer(300, this); /** * constructor. * @param menu */ public MyMenuListener(final JMenu menu) { this.menu = menu; } /** * this happens when the user clicks the menu, but before the pop up * menu appears. * @param e */ @Override public void menuSelected(MenuEvent e) { new MyMenuWorker(menu).execute(); } @Override public void menuDeselected(MenuEvent e) { } @Override public void menuCanceled(MenuEvent e) { } /** * background worker that can do long-running tasks such as database * queries and then add more menu options to the list after the list has * already been displayed. */ public class MyMenuWorker extends SwingWorker { private boolean quick; private int insertAt = 0; private final JMenu menu; public MyMenuWorker(JMenu menu) { synchronized (LOCK) { this.menu = menu; // clear out menus that were added last time for (int i = 0; i < menu.getItemCount(); i++) { final JMenuItem menuItem = menu.getItem(i); if (menuItem != null && myMenus.contains(menuItem)) { if (myMenus.remove(menuItem)) { menu.remove(menuItem); i--; } } } // add instant menus this.quick = true; } quickMenu(this); synchronized (LOCK) { this.insertAt = menu.getItemCount(); // end of list } } @Override protected Boolean doInBackground() throws Exception { synchronized (LOCK) { this.quick = false; } slowMenu(this); return null; } /** * add menu items after the menu has already been shown. these are * the menu items that are published above using publish(). * * @param menuItems menu items to add */ @Override protected void process(List menuItems) { final int originalInsert = insertAt; for (final JMenuItem item : menuItems) { myMenus.add(item); menu.add(item, insertAt++); } tempDisable(menu, originalInsert); resizeMenu(); } /** * feel free to reuse, modify, or distribute. * http://javajon.blogspot.com/ */ private void resizeMenu() { final JPopupMenu popupMenu = menu.getPopupMenu(); if (popupMenu.isVisible()) { // redraw the menu menu.revalidate(); // resize the menu popupMenu.setVisible(false); popupMenu.setVisible(true); } } /** * report errors. */ @Override protected void done() { try { get(); } catch (InterruptedException ex) { Logger.getLogger(SwingWorkerMenuBar.class.getName()).log(Level.INFO, null, ex); } catch (ExecutionException ex) { Logger.getLogger(SwingWorkerMenuBar.class.getName()).log(Level.INFO, null, ex); } } /** * when called from quickMenu() this method * * @param item */ void add(JMenuItem item) { if (quick) { myMenus.add(menu.add(item)); } else { publish(item); } } } /** * temporarily disables menu options because they are changing and you * don't want the user to click and have the menu option change just * before the click is made. * * @param menu * @param firstIndex */ private void tempDisable( final JMenu menu, final int firstIndex) { synchronized (LOCK) { int insertAt = 0; for (int i = firstIndex; i < menu.getItemCount(); i++) { final JMenuItem menuItem = menu.getItem(i); if (menuItem.isEnabled()) { menuItem.setEnabled(false); if (!menusToEnable.contains(menuItem)) { menusToEnable.add(insertAt++, menuItem); } } } timer.start(); } } /** * this is the code that re-enables menus when they have been disabled * to prevent accidental clicking. this is the ActionListener * implementation for the timer task. * * @param e */ @Override public void actionPerformed(ActionEvent e) { synchronized (LOCK) { if (menusToEnable.isEmpty()) { /** * if there's nothing to do, kill the timer. */ timer.stop(); } else { /** * re-enable the menus. */ SwingUtilities.invokeLater(new Runnable() { @Override public void run() { synchronized (LOCK) { if (!menusToEnable.isEmpty()) { // enable the first menu that's disabled for (int i = 0; i < menu.getItemCount(); i++) { final JMenuItem enableMe = menu.getItem(i); if (menusToEnable.contains(enableMe)) { // enable one custom menu and quit enableMe.setEnabled(true); enableMe.revalidate(); menusToEnable.remove(enableMe); break; } } } } } }); } // empty / kill timer } // synchronized } } /** * create a simple form with a file menu. */ private void initComponents() { setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); /** * simulate a file menu with a single menu option on it. */ final javax.swing.JMenuBar menuBar; setJMenuBar(menuBar = new javax.swing.JMenuBar()); menuBar.add(this.fileMenu = new javax.swing.JMenu("File")); fileMenu.add("Existing Menu I"); fileMenu.add("Existing Menu II"); /** * form content, form size (i think), and center on screen. */ getContentPane().setPreferredSize(new Dimension(400, 260)); pack(); setLocationRelativeTo(null); } /** * default program entry point -- generated by NetBeans. * * @param args the command line arguments */ public static void main(String args[]) { /* Create and display the form */ java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new SwingWorkerMenuBar().setVisible(true); } }); } }
No comments:
Post a Comment