使用反射简化Swing工具栏菜单按钮子项的设计
传统的Swing工具栏的按钮从生成到响应总是需要一堆相似的代码来完成的,如下:
生成工具栏按钮的代码示例:
.....
响应上面这个按钮的点击事件的代码如下:
如果只是这点代码,程序整体确实没有什么问题,但数量多了就不得了,试想想看,按照MVC的放置原则,生成的代码和响应点击的代码应该放在两个类中,如果需要修改就是两个修改点;而工具栏按钮一般是较多的,假设有十个按钮那么就意味着十对修改点,如果数量再多呢?这对软件的可维护性可谓一个灾难,也为系统的隐患埋下了伏笔。有人说这就要考验维护者对系统的理解和对代码的责任心了,对这种转移问题和把维护人员等同于机器的观点作者不敢苟同,框架的设计者不能把自己应该尽到的责任推卸给维护者。
如果有效利用XML和反射等手段,我们可以做到工具栏菜单项的可配置化。具体来说就是,将菜单项的文字,图片和点击后的响应函数都在XML配置文件中配置好,程序启动时去读取文件生成菜单,点击菜单项后会动态的通过反射找到具体需要处理的函数。这样做以后,修改一个按钮对应的功能定位代码,或是增删一个按钮及其功能就很容易了。下面来看具体的做法:
1.工具栏菜单的配置文件
以上子项里,icon是工具栏按钮的对应图片,function是点击按钮后响应的具体函数名,description是用于ToolTipText的文字,这些信息都会被程序读取出来。
2.读取配置文件中的信息
3.根据读出的信息生成按钮项并添加到工具栏。
下面就是添加完成的工具栏:
ToolbarItemLoader.getItems()会得到上面第二段代码中的items的引用,然后遍历得到子项后就可以生成按钮了。但注意一下,生成的按钮是自定义的ToolbarButton类实例而不是JButton的子类实例,ToolbarButton继承自JButton但多了一个function属性,这个属性对于事件响应是必不可少的,请看下面的代码:
4.添加工具栏菜单按钮的事件响应:
上面代码就显示了ToolbarButton的function属性,它作为一个函数的参数传了出去,这个函数用反射来具体定位函数,具体来说就是,function就是要真正调用的函数名。
5.用反射调用具体的函数executeFunction.
通过以上的反射处理后,如果点击的是
这段信息对应的按钮,那么本类的format函数就会得到调用。
上面需要注意的有几点:
一是被反射调用的函数的访问权限需要公有,否则会发生IllegalAccessException异常;二是函数名一定要和配置文件中的function节点对应好,否则会抛出NoSuchMethodException异常,三是函数有自己的异常抛出后,需要用e.getTargetException()取得其真正异常。
通过以上五步,按钮的生成和响应就可以被一个配置文件所控制,这种方式改善了程序的可维护性,反射是实现改进的五个步骤的核心环节。另外反射也是诸多框架和通用性组件的常用技术之一,值得每个程序员好好掌握。
就是这些,再见吧!
生成工具栏按钮的代码示例:
.....
reopenBtn
=
new
JButton(ResourceUtil.ToolbarMain_Reopen_ImageIcon);
reopenBtn.setToolTipText( " 刷新数据库内容 " );
toolbar.add(reopenBtn); // toolbar是工具栏,JToolBar的示例
reopenBtn.setToolTipText( " 刷新数据库内容 " );
toolbar.add(reopenBtn); // toolbar是工具栏,JToolBar的示例
响应上面这个按钮的点击事件的代码如下:
view.getToolbarPanel().getReopenBtn().addActionListener(
new
ActionListener() {
public void actionPerformed(ActionEvent e) {
.. // 具体处理略
}
});
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 >
< 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。
// 下面开始读取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();
}
3.根据读出的信息生成按钮项并添加到工具栏。
for
(ToolbarItem item : ToolbarItemLoader.getItems()) {
toolbar.add( new ToolbarButton(item.getIcon(), item.getFunction(),
item.getDescription()));
}
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);
}
});
}
}
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 );
}
}
// 利用反射去找对应函数
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 >
< 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()取得其真正异常。
通过以上五步,按钮的生成和响应就可以被一个配置文件所控制,这种方式改善了程序的可维护性,反射是实现改进的五个步骤的核心环节。另外反射也是诸多框架和通用性组件的常用技术之一,值得每个程序员好好掌握。
就是这些,再见吧!