使用反射简化Swing工具栏菜单按钮子项的设计

使用反射简化Swing工具栏菜单按钮子项的设计
传统的Swing工具栏的按钮从生成到响应总是需要一堆相似的代码来完成的,如下:
生成工具栏按钮的代码示例:
.....
reopenBtn = new  JButton(ResourceUtil.ToolbarMain_Reopen_ImageIcon);
reopenBtn.setToolTipText(
" 刷新数据库内容 " );
toolbar.add(reopenBtn);
//  toolbar是工具栏,JToolBar的示例


响应上面这个按钮的点击事件的代码如下:
view.getToolbarPanel().getReopenBtn().addActionListener( new  ActionListener() {
    
public   void  actionPerformed(ActionEvent e) {
        .. 
//  具体处理略
    }
});


如果只是这点代码,程序整体确实没有什么问题,但数量多了就不得了,试想想看,按照MVC的放置原则,生成的代码和响应点击的代码应该放在两个类中,如果需要修改就是两个修改点;而工具栏按钮一般是较多的,假设有十个按钮那么就意味着十对修改点,如果数量再多呢?这对软件的可维护性可谓一个灾难,也为系统的隐患埋下了伏笔。有人说这就要考验维护者对系统的理解和对代码的责任心了,对这种转移问题和把维护人员等同于机器的观点作者不敢苟同,框架的设计者不能把自己应该尽到的责任推卸给维护者。

如果有效利用XML和反射等手段,我们可以做到工具栏菜单项的可配置化。具体来说就是,将菜单项的文字,图片和点击后的响应函数都在XML配置文件中配置好,程序启动时去读取文件生成菜单,点击菜单项后会动态的通过反射找到具体需要处理的函数。这样做以后,修改一个按钮对应的功能定位代码,或是增删一个按钮及其功能就很容易了。下面来看具体的做法:

1.工具栏菜单的配置文件
    < items >     
        
            
        
< item >
            
< icon > tollbar_sqlwindow/run.gif </ icon >
            
< function > run </ function >
            
< description > 执行所选择的Sql语句 </ description >
        
</ item >
        
        
< item >
            
< icon > tollbar_sqlwindow/batchRun.gif </ icon >
            
< function > batchRun </ function >
            
< description > 批量执行所选择的Sql语句,以分号为分割单位 </ description >
        
</ item >
        
        
< item >
            
< icon > tollbar_sqlwindow/format.gif </ icon >
            
< function > format </ function >
            
< description > 格式化所选择的Sql语句 </ description >
        
</ item >
        
        
    
</ items >
 
以上子项里,icon是工具栏按钮的对应图片,function是点击按钮后响应的具体函数名,description是用于ToolTipText的文字,这些信息都会被程序读取出来。

2.读取配置文件中的信息
    List < ToolbarItem >  items = new  ArrayList < ToolbarItem > ();
    
    
//  下面开始读取sqlWndToolbar.xml得到菜单项
     try  {
        SAXReader reader 
=   new  SAXReader();
        InputStream is
= TreeMenuPanel. class .getResourceAsStream(“sqlWndToolbar.xml”); //  toolbar.xml是具体文件名
        
        Document document 
=  reader.read(is);
        Element rootElm 
=  document.getRootElement();
        
        List
< Element >  elms  =  rootElm.elements( " item " );
        
for  (Element elm : elms) {
            String icon
= elm.elementText( " icon " );
            String function
= elm.elementText( " function " );
            String description
= elm.elementText( " description " );
            
            ToolbarItem btn
= new  ToolbarItem(icon,function,description);
            items.add(btn);
        }    
    } 
catch  (Exception ex) {
        DlgUtil.popupErrorDialog(
" 无法读取文件 " + ResourceUtil.SqlWndToolbar_XMLFile);
        ex.printStackTrace();
    }
    上述代码中,ToolbarItem是包括icon,function,description 三个字符串子项的JavaBean。
    
3.根据读出的信息生成按钮项并添加到工具栏。
   
    for  (ToolbarItem item : ToolbarItemLoader.getItems()) {
        toolbar.add(
new  ToolbarButton(item.getIcon(), item.getFunction(),
                item.getDescription()));
    }

下面就是添加完成的工具栏:


    ToolbarItemLoader.getItems()会得到上面第二段代码中的items的引用,然后遍历得到子项后就可以生成按钮了。但注意一下,生成的按钮是自定义的ToolbarButton类实例而不是JButton的子类实例,ToolbarButton继承自JButton但多了一个function属性,这个属性对于事件响应是必不可少的,请看下面的代码:
    
4.添加工具栏菜单按钮的事件响应:
for  (Component c : toolbar.getComponents()) {
    
if  (c  instanceof  ToolbarButton) {
        
final  ToolbarButton btn  =  (ToolbarButton) c;

        btn.addActionListener(
new  ActionListener() {
            
public   void  actionPerformed(ActionEvent e) {
                
//  要执行的函数
                String function  =  btn.getFunction();

                
//  所选择的文本
                String selectedText  =  inputTxt.getSelectedText();

                
//  执行函数
                executeFunction(function, selectedText);
            }
        });
    }
}

上面代码就显示了ToolbarButton的function属性,它作为一个函数的参数传了出去,这个函数用反射来具体定位函数,具体来说就是,function就是要真正调用的函数名。

5.用反射调用具体的函数executeFunction.
private   void  executeFunction(String fucntionName, String selectedText) {
    
//  利用反射去找对应函数
     try  {
        
//  得到实例对应的类
        Class <?>  cls  =   this .getClass();

        
//  通过反射得到方法
        Method method  =  cls.getMethod(fucntionName,
                
new  Class[] { String. class  });

        
//  通过反射调用对象的方法
        method.invoke( this new  Object[] { selectedText });
    } 
catch  (NoSuchMethodException e) {
        
//  找不到方法时

        DlgUtil.popupErrorDialog(
" 找不到方法 "   +  fucntionName  +   " . " );
    } 
catch  (IllegalAccessException e) {
        
//  当访问权限不够时

        DlgUtil.popupErrorDialog(
" 方法 "   +  fucntionName  +   " 的访问权限不够. " );
    } 
catch  (InvocationTargetException e) {
        
//  当调用的函数抛出异常时
        Exception tragetException  =  (Exception) e.getTargetException();

        StringBuilder sb 
=   new  StringBuilder();
        sb.append(
" 调用函数 "   +  fucntionName  +   " 出现异常, " );
        sb.append(
" 具体的异常信息为 "   +  tragetException.getMessage()  +   " . " );

        SqlTextDialog dlg 
=   new  SqlTextDialog( " 调用函数 "   +  fucntionName
                
+   " 出现异常 " , sb.toString(),  400 300 );
        dlg.setVisible(
true );
    }
}

通过以上的反射处理后,如果点击的是
< item >
    
< icon > tollbar_sqlwindow/format.gif </ icon >
    
< function > format </ function >
    
< description > 格式化所选择的Sql语句 </ description >
</ item >

这段信息对应的按钮,那么本类的format函数就会得到调用。
public   void  format(String selectedText)  throws  Exception {

}

上面需要注意的有几点:
一是被反射调用的函数的访问权限需要公有,否则会发生IllegalAccessException异常;二是函数名一定要和配置文件中的function节点对应好,否则会抛出NoSuchMethodException异常,三是函数有自己的异常抛出后,需要用e.getTargetException()取得其真正异常。

通过以上五步,按钮的生成和响应就可以被一个配置文件所控制,这种方式改善了程序的可维护性,反射是实现改进的五个步骤的核心环节。另外反射也是诸多框架和通用性组件的常用技术之一,值得每个程序员好好掌握。

就是这些,再见吧!

你可能感兴趣的:(使用反射简化Swing工具栏菜单按钮子项的设计)