2013-11-08 5 views
2

Этот вопрос похож на this one. У меня есть JPopupMenu, который появляется из значка на панели задач. На этом этапе системный лоток является единственным проявлением программы. То есть других открытых окон нет, значок в системном трее - единственный способ получить доступ к программе. Я использовал по AWT PopupMenu, потому что я хотел получить систему Look and Feel, примененную к всплывающему меню - когда я использовал просто равную PopupMenu, я не мог получить Look and Feel системы, я просто продолжал получать Metal Look Look и Swing Чувствовать. Я использовал эту работу вокруг, чтобы получить такое поведение (описано here):Не удается скрыть SystemTray JPopupMenu, когда он теряет фокус

systemTrayPopupMenu = buildSystemTrayJPopupMenu(); 
trayIcon = new TrayIcon(iconImage, "Application Name", null /* Popup Menu */); 
trayIcon.addMouseListener (new MouseAdapter() { 
    @Override 
    public void mouseReleased (MouseEvent me) { 
     if (me.isPopupTrigger()) { 
      systemTrayPopupMenu.setLocation(me.getX(), me.getY()); 
      systemTrayPopupMenu.setInvoker(systemTrayPopupMenu); 
      systemTrayPopupMenu.setVisible(true); 
     } 
    } 
}; 

Когда я правой кнопкой мыши на иконке в трее, он показывает меню, и, естественно, когда я делаю выбор, меню исчезает. Однако, когда я поднимаю меню, а затем выхожу из него, он не исчезает. Чтобы это исчезло в настоящее время, я должен либо сделать выбор, либо выбрать один из пунктов меню, которые отключены.

Я попытался добавить к нему FocusListener, однако нет никаких указаний на то, что вызовы вызвали методы focusLost или focusGained. Кроме того, я не могу заставить его исчезнуть, когда другой Window получает фокус, потому что других окон нет. Поскольку это всплывающее меню происходит от TrayIcon, а не типичной кнопки, я не могу использовать упомянутое решение here, чтобы обойти FocusListener, не вызывая focusLost.

В конце концов, то, что я интересно либо:
1) Есть ли способ, чтобы получить вид системы и чувствовать себя для нормального AWT PopupMenu или
2) Есть ли способ сделать JPopupMenu? исчезают, когда он теряет фокус?


EDIT: По желанию, вот мой SSCCE:

import java.awt.*; 
import java.awt.event.*; 
import java.io.IOException; 

import javax.imageio.ImageIO; 
import javax.swing.*; 

public class SwingSystemTray { 

public static void main(String[] args) { 
    SwingUtilities.invokeLater(new Runnable() { 
     @Override 
     public void run() { 
      try { 
       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 
       new SwingSystemTray(); 
      } catch (Exception e) { 
       System.out.println("Not using the System UI defeats the purpose..."); 
       e.printStackTrace(); 
      } 
     } 
    }); 
} 

protected SystemTray systemTray; 
protected TrayIcon trayIcon; 
protected JPopupMenu systemTrayPopupMenu; 
protected Image iconImage; 

public SwingSystemTray() throws IOException { 
    iconImage = getIcon(); 
    if (SystemTray.isSupported()) { 
     systemTray = SystemTray.getSystemTray(); 
     systemTrayPopupMenu = buildSystemTrayJPopupMenu(); 
     trayIcon = new TrayIcon(iconImage, "Application Name", null /* Popup Menu */); 
     trayIcon.addMouseListener (new MouseAdapter() { 
      @Override 
      public void mouseReleased (MouseEvent me) { 
       if (me.isPopupTrigger()) { 
        systemTrayPopupMenu.setLocation(me.getX(), me.getY()); 
        systemTrayPopupMenu.setInvoker(systemTrayPopupMenu); 
        systemTrayPopupMenu.setVisible(true); 
       } 
      } 
     }); 
     try { 
      systemTray.add(trayIcon); 
     } catch (AWTException e) { 
      System.out.println("Could not place item at tray. Exiting."); 
     } 
    } 
} 

protected JPopupMenu buildSystemTrayJPopupMenu() { 
    final JPopupMenu menu = new JPopupMenu(); 
    final JMenuItem showMenuItem = new JMenuItem("Show"); 
    final JMenuItem hideMenuItem = new JMenuItem("Hide"); 
    final JMenuItem exitMenuItem = new JMenuItem("Exit"); 
    hideMenuItem.setEnabled(false); 
    ActionListener listener = new ActionListener() { 
     @Override 
     public void actionPerformed (ActionEvent ae) { 
      Object source = ae.getSource(); 
      if (source == showMenuItem) { 
       System.out.println("Shown"); 
       showMenuItem.setEnabled(false); 
       hideMenuItem.setEnabled(true); 
      } 
      else if (source == hideMenuItem) { 
       System.out.println("Hidden"); 
       hideMenuItem.setEnabled(false); 
       showMenuItem.setEnabled(true); 
      } 
      else if (source == exitMenuItem) { 
       System.exit(0); 
      } 
     } 
    }; 
    for (JMenuItem item : new JMenuItem [] {showMenuItem, hideMenuItem, exitMenuItem}) { 
     if (item == exitMenuItem) menu.addSeparator(); 
     menu.add(item); 
     item.addActionListener(listener); 
    } 
    return menu; 
} 

protected Image getIcon() throws IOException { 
    // Build the 16x16 image programmatically, start with BMP Header 
    byte [] iconData = new byte[822]; 
    System.arraycopy(new byte [] {0x42,0x4d,0x36,0x03, 0,0,0,0, 0,0,0x36,0, 
      0,0,0x28,0, 0,0,16,0, 0,0,16,0, 0,0,16,0, 24,0,0,0, 0,0,0,3}, 
      0, iconData, 0, 36); 
    for (int i = 36; i < 822; iconData[i++] = 0); 
    for (int i = 56; i < 822; i += 3) iconData[i] = -1;  
    return ImageIO.read(new java.io.ByteArrayInputStream(iconData)); 
} 
} 
+1

. Это на самом деле проблема для многих значков лотков ... поскольку у них нет концепции родительского фокуса, чтобы знать, когда фокус действительно изменился. Лучшее решение, которое я видел, - просто разместить «закрытую» опцию в конце меню и заставить пользователя закрыть его;) Я не пробовал, но я подумал о добавлении привязки клавиш к экрану эвакуации ... – MadProgrammer

+0

Опубликуйте свой 'SSCCE', который демонстрирует проблему. – camickr

ответ

3

Я нашел хак, который я чувствую, будет работать хорошо. Я еще не тестировал его в Windows XP, но он работает в Windows 7. Это связано с добавлением «скрытого диалога», который отображает за всплывающее меню, как если бы всплывающее меню происходило из скрытого диалога в первую очередь. Единственный реальный трюк - заставить скрытый диалог остаться за всплывающим меню. По крайней мере, в Windows 7 он отображается за системным лотком, поэтому вы никогда не видите его в первую очередь. В этот скрытый диалог можно добавить WindowFocusListener, и поэтому, когда вы выходите из всплывающего меню, вы также щелкаете по скрытому диалоговому окну. Я добавил эту способность к SSCCE, что я отправил ранее, чтобы показать, как добавление это работает:

package org.test; 

import java.awt.*; 
import java.awt.event.*; 
import java.io.IOException; 

import javax.imageio.ImageIO; 
import javax.swing.*; 

public class SwingSystemTray { 

public static void main(String[] args) { 
    SwingUtilities.invokeLater(new Runnable() { 
     @Override 
     public void run() { 
      try { 
       /* We are going for the Windows Look and Feel here */ 
       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 
       new SwingSystemTray(); 
      } catch (Exception e) { 
       System.out.println("Not using the System UI defeats the purpose..."); 
       e.printStackTrace(); 
      } 
     } 
    }); 
} 

protected SystemTray systemTray; 
protected TrayIcon trayIcon; 
protected JPopupMenu systemTrayPopupMenu; 
protected Image iconImage; 
/* Added a "hidden dialog" */ 
protected JDialog hiddenDialog; 

public SwingSystemTray() throws IOException { 
    iconImage = getIcon(); 
    if (SystemTray.isSupported()) { 
     systemTray = SystemTray.getSystemTray(); 
     systemTrayPopupMenu = buildSystemTrayJPopupMenu(); 
     trayIcon = new TrayIcon(iconImage, "Application Name", null /* Popup Menu */); 
     trayIcon.addMouseListener (new MouseAdapter() { 
      @Override 
      public void mouseReleased (MouseEvent me) { 
       if (me.isPopupTrigger()) { 
        systemTrayPopupMenu.setLocation(me.getX(), me.getY()); 
        /* Place the hidden dialog at the same location */ 
        hiddenDialog.setLocation(me.getX(), me.getY()); 
        /* Now the popup menu's invoker is the hidden dialog */ 
        systemTrayPopupMenu.setInvoker(hiddenDialog); 
        hiddenDialog.setVisible(true); 
        systemTrayPopupMenu.setVisible(true); 
       } 
      } 
     }); 
     trayIcon.addActionListener(new ActionListener() { 
      @Override 
      public void actionPerformed (ActionEvent ae) { 
       System.out.println("actionPerformed"); 
      } 
     }); 
     try { 
      systemTray.add(trayIcon); 
     } catch (AWTException e) { 
      System.out.println("Could not place item at tray. Exiting."); 
     } 
    } 
    /* Initialize the hidden dialog as a headless, titleless dialog window */ 
    hiddenDialog = new JDialog(); 
    hiddenDialog.setSize(10, 10); 
    /* Add the window focus listener to the hidden dialog */ 
    hiddenDialog.addWindowFocusListener(new WindowFocusListener() { 
     @Override 
     public void windowLostFocus (WindowEvent we) { 
      hiddenDialog.setVisible(false); 
     } 
     @Override 
     public void windowGainedFocus (WindowEvent we) {} 
    }); 
} 

protected JPopupMenu buildSystemTrayJPopupMenu() { 
    final JPopupMenu menu = new JPopupMenu(); 
    final JMenuItem showMenuItem = new JMenuItem("Show"); 
    final JMenuItem hideMenuItem = new JMenuItem("Hide"); 
    final JMenuItem exitMenuItem = new JMenuItem("Exit"); 
    hideMenuItem.setEnabled(false); 
    ActionListener listener = new ActionListener() { 
     @Override 
     public void actionPerformed (ActionEvent ae) { 
      /* We want to make sure the hidden dialog goes away after selection */ 
      hiddenDialog.setVisible(false); 
      Object source = ae.getSource(); 
      if (source == showMenuItem) { 
       System.out.println("Shown"); 
       showMenuItem.setEnabled(false); 
       hideMenuItem.setEnabled(true); 
      } 
      else if (source == hideMenuItem) { 
       System.out.println("Hidden"); 
       hideMenuItem.setEnabled(false); 
       showMenuItem.setEnabled(true); 
      } 
      else if (source == exitMenuItem) { 
       System.exit(0); 
      } 
     } 
    }; 
    for (JMenuItem item : new JMenuItem [] {showMenuItem, hideMenuItem, exitMenuItem}) { 
     if (item == exitMenuItem) menu.addSeparator(); 
     menu.add(item); 
     item.addActionListener(listener); 
    } 
    return menu; 
} 

protected Image getIcon() throws IOException { 
    // Build the 16x16 image programmatically, start with BMP Header 
    byte [] iconData = new byte[822]; 
    System.arraycopy(new byte [] {0x42,0x4d,0x36,0x03, 0,0,0,0, 0,0,0x36,0, 
      0,0,0x28,0, 0,0,16,0, 0,0,16,0, 0,0,16,0, 24,0,0,0, 0,0,0,3}, 
      0, iconData, 0, 36); 
    for (int i = 36; i < 822; iconData[i++] = 0); 
    for (int i = 56; i < 822; i += 3) iconData[i] = -1;   
    return ImageIO.read(new java.io.ByteArrayInputStream(iconData)); 
} 
} 

Это решение дает мне требование # 2, что я искал, который должен сделать JPopupMenu исчезают, когда оно теряет фокус на системном трее, используя внешний вид системы Windows.

Примечание: У меня нет функции JPopupMenu для работы на системном трее в CentOS/RedHat Linux. Для них мне придется просто использовать обычный AWT PopupMenu.

+0

Спасибо за t его - очень полезно. К сожалению, это не помогает мне решить проблему на OSX, но это была хорошая попытка! :) – Webreaper

0

JPopupMenu, не может быть отображен сам по себе. То есть его нужно добавить в окно. Попробуйте использовать WindowListener, а затем скройте всплывающее окно в событии windowDeactivated(). После того, как всплывающее окно видно, вы должны быть в состоянии получить окно с помощью:

Window window = SwingUtilities.windowForComonent(systemTrayPopupMenu); 
+0

SystemTray известен только awp.Popup, нет прямого ответа на этот вопрос, и кажется, что OP имеет хоккей с этим – mKorbel

-1

Я просто использовал MouseListener в меню JPopup, которое вызывает таймер Thread на выходе мыши; если мышь снова войдет, я сброшу флаг «mouseStillOnMenu». Установите значение «Thread.sleep() до сколь угодно долго вы хотите, чтобы пользователь мог выйти из меню - если нажать на аа пункт меню нормально, меню по умолчанию рядом поведение по вызывается и закрывает меню

@Override 
public void mouseEntered(MouseEvent arg0) { 
    mouseStillOnMenu = true; 

} 

@Override 
public void mouseExited(MouseEvent arg0) { 
    mouseStillOnMenu = false; 

    new Thread(new Runnable() { 

     @Override 
     public void run() { 

      try { 
       Thread.sleep(1000); //waits one second before checking if mouse is still on the menu 
      } catch (InterruptedException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
      if (!isMouseStillOnMenu()) { 
       jpopup.setVisible(false); 
      } 

     } 

    }).start(); 

}