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 List myMenus = 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