要实现个性化菜单最常用的方法是继承JMenuItem类,然后再重写paintComponent方法,但这个作法有几个不足之处,一是不能绘制菜单项在高亮色效果,二是不能重设组合键的字体颜色,三是当菜单项在有无图标时显示菜单文字时出现缩进混乱,四是复选/单选图标显示混乱。为此我特地采用BasicMenuItemUI类实现菜单项的个性化。
BasicMenuItemUI类关于绘画的方法共有四个,而且分工明确很有条理。
paint :当菜单项显示时负责更新重绘菜单项表面。
paintBackgound :绘制菜单项的背景。
paintText :绘制菜单项的文字
paintMenuItem :绘制菜单项
其实这四个方法完全中只要重写一个paint方法是继承时ComponentUI类的,也就是别的UI类都有方法,主要负责画面的更新,而paintBackground用于绘制背景,包括菜单项的高亮显示,paintText用于绘制文字,可在此方法中实现文字的缩进。paintMenuItem将在调用了paintBackground和paintText方法后显示,因此我觉得没必要再重写paintMenuItem方法。
BasicMenuItemUI类内置了一个MouseInputHandler类,可以用于实现鼠标的侦听,可以通过createMouseInputListener方法来实现诸如鼠标移入、移出、按下、释放等功能。
另外BasicMenuItemUI类还有一些属性相当实用的属性
acceleratorForeground:设置组合键字体的颜色
acceleratorSelectionForeground:设置组合键处于高亮时的字体颜色
acceleratorFont:设置组合键文字字体,不过实际测试中没有效果。
另外为了能让子菜单项和其他菜单项拥有相同的界面样式,也可以把用BasicMenuItemUI类实现的子类用于JMenu上,以方便统一的菜单界面样式,但这样做的同时也失去了JMenu自动弹出子菜单的功能,因此可以用再实现一个继承了BasicMenuUI的子类。
这是我做一个简单的演示,一些相关的代码我都做了注解,毕竟代码并不复杂。
import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.PopupMenu; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButtonMenuItem; import javax.swing.JSeparator; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.plaf.basic.BasicPopupMenuUI; public class MainFrame extends JFrame { private JMenuBar menuBar; private JMenu mnuFile; public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable(){ public void run() { new MainFrame(); } }); } public MainFrame() { this.setTitle("个性化菜单演示"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setContentPane(initPanel()); this.pack(); this.setLocationRelativeTo(null); this.setVisible(true); if(menuBar == null) addMenuBar(); this.setJMenuBar(menuBar); } private JPanel initPanel() { JPanel pa = new JPanel(); pa.setPreferredSize(new Dimension(500,400)); return pa; } private void addMenuBar() { menuBar = new JMenuBar(); menuBar.add(mnuFile = addMenu("文件(F)",KeyEvent.VK_F)); mnuFile.add(addMenuItem("新建(N)",KeyEvent.VK_N,null,"newfile.gif")); mnuFile.add(addMenuItem("打开(O)...",KeyEvent.VK_O,"ctrl O","open.gif")); JMenu mnuChk = addSubMenu("复选项(C)",KeyEvent.VK_C); mnuFile.add(mnuChk); mnuChk.add(addCheckBoxMenuItem("复选项一(A)",KeyEvent.VK_A,null,null,true)); mnuChk.add(addCheckBoxMenuItem("复选项二(B)",KeyEvent.VK_B,null,null,false)); mnuChk.add(addCheckBoxMenuItem("复选项三(C)",KeyEvent.VK_C,null,null,false)); JMenu mnuRadio = addSubMenu("单选项(R)",KeyEvent.VK_R); mnuFile.add(mnuRadio); ButtonGroup bg = new ButtonGroup(); JRadioButtonMenuItem jRadio1 = addRadioMenuItem("单选项-(A)",KeyEvent.VK_A,null,null,true); JRadioButtonMenuItem jRadio2 = addRadioMenuItem("单选项二(B)",KeyEvent.VK_B,null,null,false); JRadioButtonMenuItem jRadio3 = addRadioMenuItem("单选项三(C)",KeyEvent.VK_C,null,null,false); bg.add(jRadio1); bg.add(jRadio2); bg.add(jRadio3); mnuRadio.add(jRadio1); mnuRadio.add(jRadio2); mnuRadio.add(jRadio3); mnuFile.addSeparator(); mnuFile.add(addMenuItem("退出(X)",KeyEvent.VK_X,"ctrl alt X",null)); //遍历文件菜单下的所有菜单项,重新菜单分隔栏的界面样式 for(int i = 0; i < mnuFile.getMenuComponentCount(); i++) { String className = menuBar.getMenu(0).getMenuComponent(i).getClass().getName(); if(className.equals("javax.swing.JPopupMenu$Separator")) { JPopupMenu.Separator sep = (JPopupMenu.Separator)menuBar.getMenu(0).getMenuComponent(i); sep.setUI(new GradientSeparator()); } } } private JMenu addMenu(String name,int memkey) { JMenu menu = new JMenu(name); menu.setFont(new Font("宋体",Font.PLAIN,12)); menu.setMnemonic(memkey); JPopupMenu popMenu = menu.getPopupMenu(); popMenu.setBorder(BorderFactory.createLineBorder(new Color(145, 100, 55))); return menu; } private JMenu addSubMenu(String name,int memkey) { JMenu menu = addMenu(name,memkey); menu.setUI(new MySubMenuUI()); return menu; } private JMenuItem addMenuItem(String name,int memkey,String strokeKey,String icon) { JMenuItem mi = new JMenuItem(); Action ac = new MenuAction(name,memkey,strokeKey,icon){ public void actionPerformed(ActionEvent arg0) { } }; mi.setAction(ac); mi.setUI(new MyMenuItemUI()); return mi; } private JCheckBoxMenuItem addCheckBoxMenuItem(String name,int memkey,String strokeKey,String icon,boolean selectKey) { JCheckBoxMenuItem mi = new JCheckBoxMenuItem(); Action ac = new MenuAction(name,memkey,strokeKey,icon,selectKey){ public void actionPerformed(ActionEvent arg0) { } }; mi.setAction(ac); mi.setUI(new MyMenuItemUI()); return mi; } private JRadioButtonMenuItem addRadioMenuItem(String name,int memkey,String strokeKey,String icon,boolean selectKey) { JRadioButtonMenuItem mi = new JRadioButtonMenuItem(); Action ac = new MenuAction(name,memkey,strokeKey,icon,selectKey){ public void actionPerformed(ActionEvent arg0) { System.out.println(this.getValue(Action.SELECTED_KEY)); } }; mi.setAction(ac); mi.setUI(new MyMenuItemUI()); return mi; } abstract class MenuAction extends AbstractAction { public MenuAction(String name,int memkey,String strokeKey,String icon) { this.putValue(Action.NAME, name); this.putValue(Action.MNEMONIC_KEY, memkey); if(strokeKey != null) this.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(strokeKey)); if(icon != null) this.putValue(Action.SMALL_ICON, new ImageIcon(this.getClass().getClassLoader().getResource(icon))); } public MenuAction(String name,int memkey,String strokeKey,String icon,boolean selectKey) { this(name,memkey,strokeKey,icon); this.putValue(Action.SELECTED_KEY, selectKey); } } }
import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.MouseEvent; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.MenuSelectionManager; import javax.swing.event.MouseInputListener; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.basic.BasicMenuItemUI; public class MyMenuItemUI extends BasicMenuItemUI{ protected int fontHeight = 12; protected int iconWidth = 16; protected boolean mouseOver; private boolean checkFlag; private boolean radioFlag; public MyMenuItemUI() { super(); //以下三个属性用于设置菜单项的组合键的字体、颜色和高亮色 this.acceleratorFont = new Font("Arial",Font.BOLD,12); this.acceleratorForeground = new Color(150,130,120); this.acceleratorSelectionForeground = Color.black; } @Override protected void installComponents(JMenuItem menuItem) { super.installComponents(menuItem); this.defaultTextIconGap = 5;//设置图标文字间距为5个像素 menuItem.setFont(new Font("宋体",Font.PLAIN,fontHeight)); menuItem.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 5)); //重新计算菜单项的尺寸 Dimension menuSize = menuItem.getPreferredSize(); menuSize.width = menuSize.width + iconWidth + defaultTextIconGap; menuItem.setPreferredSize(menuSize); String className = menuItem.getClass().getName();//得到menuItem的实际类名称 updateCheckIcon(className); } private void updateCheckIcon(String className) { //根据菜单项的类来实现菜单项该采用哪种图标 if(menuItem.getAction() == null || menuItem.getAction().getValue(Action.SELECTED_KEY) == null) return; if(className.equals("javax.swing.JCheckBoxMenuItem")) { String icon = null; if(Boolean.valueOf(menuItem.getAction().getValue(Action.SELECTED_KEY).toString())) icon = "check_on.gif"; else icon = "check_off.gif"; this.checkIcon = new ImageIcon(this.getClass().getClassLoader().getResource(icon)); } if(className.equals("javax.swing.JRadioButtonMenuItem")) { String icon = null; if(Boolean.valueOf(menuItem.getAction().getValue(Action.SELECTED_KEY).toString())) this.checkIcon = new ImageIcon(this.getClass().getClassLoader().getResource("bullet.gif")); else this.checkIcon = null; } } protected MouseInputListener createMouseInputListener(JComponent c) { MouseInputListener mil = new MouseInputHandler(){ public void mouseEntered(MouseEvent e) { super.mouseEntered(e); mouseOver = true; } public void mouseExited(MouseEvent e) { super.mouseExited(e); mouseOver = false; } public void mousePressed(MouseEvent e) { super.mousePressed(e); mouseOver = true; } public void mouseReleased(MouseEvent e) { super.mouseReleased(e); mouseOver = true; } }; return mil; } @Override public void paint(Graphics arg0, JComponent arg1) { // TODO Auto-generated method stub updateCheckIcon(menuItem.getClass().getName()); super.paint(arg0, arg1); } protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor) { super.paintBackground(g, menuItem, bgColor); //以下绘制菜单的背景 bgColor = new Color(235, 227, 212); g.setColor(bgColor); g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight()); g.setColor(Color.white); g.fillRect(0, 0, iconWidth + defaultTextIconGap * 2, menuItem.getHeight()); //以下代码将在鼠标停留时绘制 if (mouseOver) { g.setColor(new Color(238, 223, 188)); g.fillRect(3, 3, menuItem.getWidth() - 7, menuItem.getHeight() - 7); g.setColor(new Color(145, 100, 55)); g.drawRect(2, 2, menuItem.getWidth() - 5, menuItem.getHeight() - 5); } } protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect, String text) { int textOffset = this.defaultTextIconGap + iconWidth + 10; g.setColor(Color.black); g.drawString(text, textOffset, textRect.y+fontHeight); } }
import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.MouseEvent; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.MenuSelectionManager; import javax.swing.event.MouseInputListener; import javax.swing.plaf.basic.BasicMenuUI; public class MySubMenuUI extends BasicMenuUI { protected int fontHeight = 12; protected int iconWidth = 16; protected boolean mouseOver; public MySubMenuUI() { super(); this.arrowIcon = new ImageIcon(this.getClass().getClassLoader().getResource("subMenu.gif")); } protected void installComponents(JMenuItem arg0) { super.installComponents(arg0); this.defaultTextIconGap = 5; menuItem.setFont(new Font("宋体",Font.PLAIN,fontHeight)); menuItem.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 5)); } protected MouseInputListener createMouseInputListener(JComponent c) { MouseInputListener mil = new MouseInputHandler(){ public void mouseEntered(MouseEvent e) { super.mouseEntered(e); mouseOver = true; } public void mouseExited(MouseEvent e) { super.mouseExited(e); mouseOver = false; } public void mousePressed(MouseEvent e) { super.mousePressed(e); mouseOver = true; } public void mouseReleased(MouseEvent e) { super.mouseReleased(e); mouseOver = true; } }; return mil; } @Override protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor) { super.paintBackground(g, menuItem, bgColor); //以下绘制菜单的背景 bgColor = new Color(235, 227, 212); g.setColor(bgColor); g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight()); g.setColor(Color.white); g.fillRect(0, 0, iconWidth + defaultTextIconGap * 2, menuItem.getHeight()); //以下代码将在鼠标停留时绘制 if (mouseOver) { g.setColor(new Color(238, 223, 188)); g.fillRect(3, 3, menuItem.getWidth() - 7, menuItem.getHeight() - 7); g.setColor(new Color(145, 100, 55)); g.drawRect(2, 2, menuItem.getWidth() - 5, menuItem.getHeight() - 5); } } protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect, String text) { int textOffset = this.defaultTextIconGap + iconWidth + 10; g.setColor(Color.black); g.drawString(text, textOffset, textRect.y+fontHeight); } }
import java.awt.Color; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import javax.swing.JComponent; import javax.swing.plaf.basic.BasicSeparatorUI; public class GradientSeparator extends BasicSeparatorUI { private Color color1 = new Color(145, 100, 55); private Color color2 = new Color(238, 223, 188); public GradientSeparator() { } public void paint(Graphics g, JComponent c) { // TODO Auto-generated method stub Graphics2D g2 = (Graphics2D)g; GradientPaint gp1 = new GradientPaint(0,0, color2, c.getWidth()/2,0, color1); g2.setPaint(gp1); g2.drawLine(10, 0, c.getWidth()/2, 0); GradientPaint gp2 = new GradientPaint(c.getWidth()/2,0, color1, c.getWidth(),0, color2); g2.setPaint(gp2); g2.drawLine(c.getWidth()/2, 0, c.getWidth()-10, 0); } }