JConsole是一个基于JMX的GUI工具,用于连接正在运行的JVM,它是一个MDI风格的Java桌面应用。SWing实现MDI风格界面的基础是JInternalFrame和JDesktopPane。那么如何基于SWT实现MDI风格界面呢?Eclipse平台中我们找不到这样的实现做参考(如果哪位网友发现有个非常好的这类实现一定要告诉我),但实际上SWT还是提供了类似的实现基础---那就是Decorations。从下图中的类继承关系看,既然Decorations是一个Control,而且没有说它不可以嵌到其他Composite中去,那它就应该可以(它的子类Shell明确声明为不可以)。
创建一个View作为主界面是个理所当然的选择,使用ViewMenu和ToolBar实现Sun Jconsole提供的菜单项(Help除外):
下图为Sun的界面与本人实现的界面对比图:
对于目前实现的demo来说最重要的2个类就是MyJConsoleView和VMInternalFrame,其中VMInternalFrame继承Decorations并于Sun JConsole中的命名保持一致,MyJConsoleView的菜单中固定不变部分定义在plugin.xml中,而随着内部窗口增加、删除,对应的菜单项也将动态加到菜单中或从菜单中删除。
private Map<VMInternalFrame, AboveFrameAction> windows = new LinkedHashMap<VMInternalFrame, AboveFrameAction>(); public MyJConsoleView() { } @Override public void createPartControl(Composite parent) { container = new Composite(parent, SWT.NONE); initializeMenu(); setPartName("Java Monitoring & Management Console"); } private void initializeMenu() { final IMenuManager manager = getViewSite().getActionBars().getMenuManager(); manager.add(new Separator()); manager.add(new Separator("layout")); manager.add(new Separator()); manager.add(new Separator("connections")); } private void refeshViewMenu(){ final IMenuManager manager = getViewSite().getActionBars().getMenuManager(); IContributionItem[] items = manager.getItems(); for (int i = 0; i < items.length; i++) { if(items[i] instanceof ActionContributionItem){ IAction action = ((ActionContributionItem)items[i]).getAction(); if(!(action instanceof IPluginContribution)){ manager.remove(items[i]); } } } for (VMInternalFrame frame : windows.keySet()) { manager.appendToGroup("connections", windows.get(frame)); } }
对于MDI风格界面层铺和平铺布局至关重要,本实例中采用了和Sun JConsole相类似的布局算法。
private void cascadeWindows(){ int num = windows.size(); if(num > 0){ Point childSize = windows.keySet().iterator().next().getPreferredSize(); Point parentSize = container.getSize(); int dx = (parentSize.x - childSize.x)/(num==1?1:num - 1); int dy = (parentSize.y - childSize.y)/(num==1?1:num - 1); int i=0; for (VMInternalFrame frame : windows.keySet()) { frame.setToAbove(); frame.setBounds(dx*i, dy*i, childSize.x, childSize.y); i++; } } } private void tileWindows(){ int num = windows.size(); int rows = (int)Math.ceil(Math.sqrt(num)); int cols = num / rows; if (rows * cols < num) cols++; int x = 0; int y = 0; Point parentSize = container.getSize(); int width = parentSize.x/cols; int height = parentSize.y/rows; int col = 0; for (VMInternalFrame frame : windows.keySet()) { frame.setToAbove(); frame.setBounds(x, y, width, height); frame.setMaximized(num==1); if (col < cols-1) { col++; x += width; } else { col = 0; x = 0; y += height; } } }
对于MDI界面,当有内部窗口处于最大化状态时,如果外部窗体(我们这就是view)的大小发生变化或最大化时,这个内部窗口也应该随之改变大小。为此VMInternalFrame实现了ControlListener用于监听view的变化。
@Override public void controlResized(ControlEvent e) { final Control control = (Control)e.getSource(); if(control == getParent() && isMax){ isMax = false; removeControlListener(controlListener); setMaximized(false); getShell().getDisplay().asyncExec(new Runnable() { public void run() { setSize(new Point(control.getSize().x, control.getSize().y)); setMaximized(true); setFocus(); addControlListener(controlListener); isMax = true; } }); } }
在controlResized()方法中式所以用到了asyncExec是因为如果不这样当view最大化时,处于最大化状态的内部窗口并不随之变化。而定义变量isMax的原因是getMaximized()方法有问题,当view最大化时,处于normal状态的内部窗口的getMaximized()也返回true(我没有细究其中的原因,如果哪位细心的网友发现原因别忘了告诉我)。
注意:继承Decorations的类必须重载checkSubclass (),否则SWT验证通不过。
问题:
Decorations实现的内部窗口存在一些问题,有些甚至是目前无法解决的,以至于eclipse官方不建议使用Decorations。我发现2个比较重要的问题:
1. Decorations在创建后style无法变更,因此不能在最大化时与主窗口融合。
2. 因为在View中创建了Decorations,在退出eclipse时需要在“Confirm Exit”对话框上多点几下才能获取焦点,直接点“OK”按钮根本没反应。
网上有人建议使用ViewForm来实现内部窗口,但找不到一个像样的实例,用它模拟出内部窗口所具有的全部特性肯定需要费些周折。接下来我将使用Decorations先实现JConsole的基本功能(1.0),而在2.0中采用ViewForm模拟内部窗口。
关于源代码的声明:
本实例源代码中有一部分可能直接来自SUN JConsole,因此如果用于商业目的而引起知识产权纠纷,本人概不负责。另外所用图标均非本人创建,在此向图标设计者表示感谢。