| <?xml version="1.0" encoding="utf-8"?> |
| <!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" |
| "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> |
| <article id="article"> |
| <articleinfo> |
| <title>Swing/SWT Integration</title> |
| <releaseinfo role="SVN"> $Id: article.xml,v 1.3 2007/06/20 15:33:53 wbeaton Exp $ </releaseinfo> |
| <date>June 20, 2007</date> |
| <authorgroup> |
| <author> |
| <firstname>Gordon</firstname> |
| <surname>Hirsch</surname> |
| <affiliation> |
| <orgname>SAS Institute Inc.</orgname> |
| </affiliation> |
| </author> |
| </authorgroup> |
| <copyright> |
| <year>2007</year> |
| <holder>SAS Institute Inc. Made available under the EPL v1.0 </holder> |
| </copyright> |
| <abstract> |
| <para> |
| Swing and SWT are sometimes seen as strictly competing technologies. Some |
| people have strong opinions on which UI toolkit to use exclusively for client |
| applications. However, in the real world, ideological extremes are |
| often impractical. Some valid use cases require both technologies |
| to coexist in a single application. While mixing the two toolkits is not a |
| simple task, it can be done, and it can be done such that the two toolkits |
| are smoothly integrated. This article discusses the steps necessary to |
| achieve good Swing/SWT integration. It focuses on the use case of embedding |
| existing Swing components into an SWT-based Rich Client Platform application. |
| </para> |
| </abstract> |
| <legalnotice> |
| <para>Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in |
| the United States, other countries, or both.</para> |
| <para>Other company, product, or service names may be trademarks or service marks of |
| others.</para> |
| </legalnotice> |
| </articleinfo> |
| <section id="sec-introduction"> |
| <title>Introduction</title> |
| |
| <para> |
| Many existing standalone Java clients represent a large investment in Swing |
| components. While there are compelling arguments for moving these clients to the Eclipse Rich |
| Client Platform (RCP), the full migration of a large existing application can be |
| expensive. By retaining some Swing components, the initial cost of |
| migration can be reduced. Over time, Swing components may be incrementally converted |
| to SWT if and when it becomes necessary. |
| </para> |
| <para> |
| For example, at SAS we have extended the standard Swing <ulink url="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTable.html"> |
| <code>JTable</code></ulink> component to |
| create an enhanced table that meets the needs of our existing applications. |
| A large effort will be required to convert this component to SWT. Instead of |
| waiting for an equivalent SWT table, we are able to use the Swing |
| table component as-is in an RCP application, as shown in the screenshot below. |
| It shows an RCP application running on the Windows XP platform. |
| The two tables are Swing components; all other components |
| were created with SWT. |
| </para> |
| <figure id="fig-table-embed"> |
| <title>Embedding Swing Table Components</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/table-embed.png"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| We also have a large inventory of complex Swing components to display graphs and charts. |
| Again, by mixing Swing and SWT, we can consider the conversion of these Swing components separately from |
| the immediate needs of an application that we'd like to migrate to RCP. |
| The screenshot below shows another RCP application with a Swing-based graph |
| component. Everything else in the image is an |
| SWT component. (This application may not look like an RCP application, |
| but it is. The unusual look is due to extensive use of the Eclipse |
| presentations API and other advanced workbench features.) |
| </para> |
| <figure id="fig-graph-embed"> |
| <title>Embedding Graphical Swing Components</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/graph-embed.jpg"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| The use of Swing components with the RCP implies that two large UI toolkits will coexist in a |
| single application and that some amount of integration is necessary to ensure UI |
| consistency across the entire application. |
| </para> |
| <para> |
| SWT provides the SWT/AWT Bridge <xref linkend="bib-bridge"/> as the basic infrastructure for Swing/SWT integration. |
| However, the bridge is only the first step along the |
| path to embedding usable Swing components in RCP applications. Much of |
| this article is devoted to explaining the additional practices necessary for |
| effective integration. Without these practices, the |
| SWT/AWT Bridge is insufficient for production-quality application use. |
| With them, you can successfully host key Swing components in RCP |
| applications. |
| </para> |
| <para> |
| Integrating two independent UI toolkits is a complicated task, and it |
| rarely produces perfect results. This article |
| concentrates on practical techniques (and yes, even some hacks) to make the |
| integration effective enough for use in real-world applications. |
| </para> |
| <para> |
| Despite the complexity, most of the practices described below can be encapsulated |
| in a single common base class to be used when embedding Swing components. There are additional |
| helper classes to support the implementation. An example is included with this |
| article. See <xref linkend="app-example-code"/> for more information. |
| </para> |
| </section> |
| <section id="sec-using-bridge"> |
| <title>Using the SWT/AWT Bridge</title> |
| <para> |
| The SWT/AWT Bridge has been part of SWT since version 3.0. It is |
| a very simple API, located in the package |
| <code> |
| <ulink |
| url="http://help.eclipse.org/help32/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/awt/package-summary.html"> |
| org.eclipse.swt.awt</ulink>. |
| </code> |
| </para> |
| <note> |
| <para> |
| This article focuses on embedding AWT frames inside SWT composites. It demonstrates only |
| one half of the SWT/AWT Bridge. Nevertheless, most of the improvements described below also apply to the |
| other direction: embedding SWT composites inside AWT frames. |
| </para> |
| </note> |
| <para>Minimally, embedding an AWT frame inside an SWT composite is just two simple lines of code</para> |
| <programlisting> |
| Composite composite = new Composite(parent, SWT.EMBEDDED | SWT.NO_BACKGROUND); |
| Frame frame = SWT_AWT.new_Frame(composite); |
| </programlisting> |
| <para> |
| An instance of <code>org.eclipse.swt.Composite</code> is created with the <code>SWT.EMBEDDED</code> |
| style. This style signals that an AWT frame is to be embedded inside the Composite. The call to the static <code>new_Frame</code> |
| method creates and returns such a frame. The frame may then be |
| populated with AWT and/or Swing components. |
| </para> |
| <para> |
| The returned frame is not a standard AWT frame. By default, it is a subclass of <code>java.awt.Frame</code> |
| that is meant to be embedded within native applications. In fact, it is the same frame that is used to |
| embed <ulink url="http://java.sun.com/applets/">applets</ulink> inside a browser window. |
| </para> |
| <para> |
| The example code shown above is deceptively simple. While SWT does much under the covers to manage the |
| integration of the two toolkits, the scope of the bridge's implementation is very narrow. In reality, you must do much |
| more in your application to make the integration more consistent. These additional steps are |
| described below in <xref linkend="sec-adding-to-bridge"/>. |
| </para> |
| <section id="sec-platform"> |
| <title>Platform Considerations</title> |
| <para> |
| There are version constraints for using the SWT/AWT Bridge that differ from platform to |
| platform. |
| </para> |
| <table id="version-table" frame='all'> |
| <title>Minimum Versions</title> |
| <tgroup cols='3' align='left' colsep='1' rowsep='1'> |
| <colspec colname='c1' /> |
| <colspec colname='c2' /> |
| <colspec colname='c3' /> |
| <thead> |
| <row> |
| <entry>Platform</entry> |
| <entry>JRE Version</entry> |
| <entry>Eclipse Version</entry> |
| </row> |
| </thead> |
| <tbody> |
| <row> |
| <entry>Windows</entry> |
| <entry>1.4</entry> |
| <entry>3.0</entry> |
| </row> |
| <row> |
| <entry>Linux/GTK</entry> |
| <entry>1.5</entry> |
| <entry>3.0</entry> |
| </row> |
| <row> |
| <entry>Mac OS X</entry> |
| <entry>1.5.0 Release 4</entry> |
| <entry>3.2</entry> |
| </row> |
| </tbody> |
| </tgroup> |
| </table> |
| <note> |
| <para> |
| Mac OS X also requires installation of the SWT Compatibility |
| Libraries. See the SWT |
| <ulink url="http://www.eclipse.org/swt/faq.php#swtawtosx">FAQ</ulink> |
| for details. Also, as of the writing of this article, the SWT/AWT Bridge on |
| Mac OS X is not yet complete. See bug |
| <ulink url="https://bugs.eclipse.org/bugs/show_bug.cgi?id=145890"> |
| 145890 |
| </ulink> |
| for details. |
| </para> |
| </note> |
| <note> |
| <para> |
| As of the writing of this article, use of the SWT/AWT Bridge causes hangs |
| with Java 6 with the GTK look and feel on the Linux/GTK platform. Refer to |
| Eclipse bug |
| <ulink url="https://bugs.eclipse.org/bugs/show_bug.cgi?id=91157">91157</ulink> |
| and Sun bug |
| <ulink url="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6386791"> |
| 6386791 |
| </ulink> |
| for more information. |
| </para> |
| </note> |
| </section> |
| <section id="sec-event-threads"> |
| <title>Multiple Event Threads</title> |
| <para> |
| Swing/SWT integration has important threading implications. Each UI toolkit has its own |
| event queue, and each event queue is processed by a separate thread. Most SWT |
| APIs must be called from the SWT event thread. Swing has similar |
| restrictions though they are not as strictly enforced. |
| This split is the major drawback of mixing the toolkits, and it adds some |
| complexity to the code. |
| </para> |
| <para> |
| Applications must be aware of the current thread, and, where necessary, |
| schedule tasks to run on the appropriate UI toolkit thread. To schedule work on |
| the AWT event thread, use: |
| </para> |
| <itemizedlist> |
| <listitem> |
| <simpara> |
| <code>javax.swing.SwingUtilities.invokeLater()</code> |
| </simpara> |
| </listitem> |
| <listitem> |
| <simpara> |
| <code>javax.swing.SwingUtilities.invokeAndWait()</code> |
| </simpara> |
| </listitem> |
| </itemizedlist> |
| <para>To schedule work on the SWT event thread, use:</para> |
| <itemizedlist> |
| <listitem> |
| <simpara> |
| <code>org.eclipse.swt.widgets.Display.asyncExec()</code> |
| </simpara> |
| </listitem> |
| <listitem> |
| <simpara> |
| <code>org.eclipse.swt.widgets.Display.syncExec()</code> |
| </simpara> |
| </listitem> |
| </itemizedlist> |
| <para> |
| These are the same APIs used in a single-toolkit environment |
| to keep the UI responsive while offloading long running operations to a worker |
| thread. With Swing/SWT integration they are used for the |
| additional purpose of moving work from one event thread to another. |
| </para> |
| <para> |
| The use of multiple event threads increases the risk of deadlock. |
| Whenever possible, try to avoid blocking one event thread while |
| scheduling work on the other event thread. In other words, |
| avoid calling <code>SwingUtilities.invokeAndWait</code> from the SWT event |
| thread and avoid calling <code>Display.syncExec</code> from the AWT event thread. |
| Otherwise, if there's ever a case where one blocking call is made while the |
| other thread has made its own blocking call in the other direction, |
| deadlock will occur. |
| </para> |
| </section> |
| </section> |
| <section id="sec-adding-to-bridge"> |
| <title>Building on the SWT/AWT Bridge</title> |
| <para> |
| If you use the SWT/AWT Bridge API alone, Swing components will integrate poorly |
| into your application. The following sections |
| describe the integration problems in detail and suggest solutions. All of the solutions |
| involve additional code to help manage the Swing/SWT integration. The |
| good news is that this code can be encapsulated in a single custom |
| embedded composite widget (and some support classes), and it can be decoupled from |
| the rest of the application |
| code. |
| </para> |
| <section id="sec-look-and-feel"> |
| <title>Configuring the Swing Look and Feel</title> |
| <para> |
| Here's an example showing some very basic visual problems that result from |
| minimal use of the SWT/AWT Bridge on the Windows platform. |
| </para> |
| <figure id="fig-badembed"> |
| <title>Minimal Embedding of a Swing Component</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/badembed.jpg"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| There are some immediately obvious problems here. The header and scrollbars on the Swing |
| <code>JTable</code> (upper right) differ from the SWT-based Error Log view. |
| </para> |
| <para> |
| In this example, the <code>JTable</code> is displayed with the standard |
| Swing cross-platform (Metal) |
| <ulink |
| url="http://java.sun.com/docs/books/tutorial/ui/features/plaf.html"> |
| look and feel |
| </ulink>. |
| On the other hand, the |
| SWT components use underlying native widgets |
| which have a native look and feel. Swing's |
| look and feel must be changed to match the |
| native platform. Since the SWT/AWT Bridge itself does not automatically set |
| the native look and feel, it's necessary to do it yourself. For |
| example, |
| </para> |
| <programlisting> |
| import javax.swing.UIManager; |
| ... |
| UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); |
| ... |
| </programlisting> |
| <para> |
| Make this call once, before any |
| AWT or Swing components are created. |
| </para> |
| <tip> |
| <para> |
| Linux/GTK developers: depending on the window manager in use, the Swing system |
| look and feel may not be set to the GTK look and feel. It may be necessary to |
| set the GTK look and feel more explicitly: |
| <programlisting> |
| UIManager.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel"); |
| </programlisting> |
| </para> |
| </tip> |
| </section> |
| <section id="sec-root-pane-container"> |
| <title>Creating a Root Pane Container</title> |
| <para> |
| The |
| <code>SWT_AWT.new_Frame()</code> |
| method returns an instance of a subclass of <code>java.awt.Frame</code> that has been embedded within an SWT |
| <code>Composite</code>. There are certain rules that must be followed when using |
| an embedded frame in Swing. |
| </para> |
| <itemizedlist> |
| <listitem> |
| <para> |
| The first child of the embedded frame must be a heavyweight component. The |
| component must fill the entire frame. This heavyweight component allows for correct mouse locations and |
| interactions. See Sun bug <ulink url="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4982522">4982522</ulink> |
| for more information. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| To make the frame viable for use with Swing, you must also create a |
| <ulink |
| url="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JRootPane.html"> |
| root pane |
| </ulink>container (<code>javax.swing.RootPaneContainer</code>). The root pane is the basis for all Swing windows. It provides the layering |
| capabilities on which all Swing components depend. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| Due to assumptions in the Swing implementation, the root pane container |
| should be an instance of <code>JFrame</code>, <code>JDialog</code>, <code>JWindow</code>, or <code>JApplet</code>. |
| Of these options, |
| only <code>JApplet</code> is an appropriate choice, since it is the only one that can |
| be embedded. |
| </para> |
| </listitem> |
| </itemizedlist> |
| <para> |
| To satisfy each of these rules, create a <code>JApplet</code> as the only child of the |
| embedded frame. Use of <code>JApplet</code> does not imply that you have created a true |
| applet with an applet's lifecycle; you are simply using the same display |
| container as an applet embedded in a browser. |
| </para> |
| <programlisting> |
| Composite composite = new Composite(parent, SWT.EMBEDDED | SWT.NO_BACKGROUND); |
| Frame frame = SWT_AWT.new_Frame(composite); |
| JApplet applet = new JApplet(); |
| frame.add(applet); |
| </programlisting> |
| <note> |
| <para> |
| The embedded frame is a window (a subclass of <code>java.awt.Window</code>). As such, |
| it consumes resources. For this reason we encourage the use of embedded |
| frames as replacements for larger components rather than smaller ones. |
| </para> |
| </note> |
| </section> |
| <section id="sec-reducing-flicker"> |
| <title>Reducing Flicker</title> |
| <para> |
| Use of the SWT/AWT Bridge, without additional measures, causes excessive |
| flicker in an embedded AWT frame while the application window is resized. The |
| reasons include: |
| </para> |
| <itemizedlist> |
| <listitem> |
| <para> |
| A heavyweight component (<code>javax.swing.JApplet</code>, in our case) is present in the component |
| hierarchy. The AWT implementation, by default, clears the component's background on every resize event. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| More resize events are handled by the embedded frame, when compared to the default |
| behavior in a standalone Swing application. This increase is due to the way resize |
| events are passed on to the AWT frame from the enclosing SWT composite. |
| </para> |
| </listitem> |
| </itemizedlist> |
| <para> |
| On Windows, set the property |
| <code>sun.awt.noerasebackground</code> to reduce flicker. This undocumented property |
| disables much of the repetitive |
| background clearing by the AWT implementation. It should be set before any |
| AWT or Swing components are instantiated. |
| </para> |
| <programlisting> |
| System.setProperty("sun.awt.noerasebackground", "true"); |
| </programlisting> |
| <note> |
| <para> |
| The <code>sun.awt.noerasebackground</code> property is not used on non-Windows platforms. |
| Reducing resize flicker on those platforms remains an open problem. |
| </para> |
| </note> |
| <para> |
| Though it is necessary, setting |
| <code>sun.awt.noerasebackground</code> |
| has negative consequences. Leaving the background uncleared may result in the |
| temporary display of previously drawn pixels (an example of what is sometimes called |
| <ulink url="http://inside-swt.blogspot.com/2006/08/swt-speak-part-1.html">cheese</ulink>) |
| during resize operations and before the initial Swing contents |
| are displayed. |
| </para> |
| <para> |
| These effects are removed by adding a resize listener to the parent |
| SWT composite. When the composite is resized larger, exposing an unpainted |
| region, the listener fills it immediately with the background color. The |
| cheese is removed immediately, providing a cosmetic improvement during any delay |
| while the embedded Swing component repaints. |
| </para> |
| <programlisting> |
| class CleanResizeListener extends ControlAdapter { |
| private Rectangle oldRect = null; |
| public void controlResized(ControlEvent e) { |
| // Prevent garbage from Swing lags during resize. Fill exposed areas |
| // with background color. |
| Composite composite = (Composite)e.widget; |
| Rectangle newRect = composite.getClientArea(); |
| if (oldRect != null) { |
| int heightDelta = newRect.height - oldRect.height; |
| int widthDelta = newRect.width - oldRect.width; |
| if ((heightDelta > 0) || (widthDelta > 0)) { |
| GC gc = new GC(composite); |
| try { |
| gc.fillRectangle(newRect.x, oldRect.height, newRect.width, heightDelta); |
| gc.fillRectangle(oldRect.width, newRect.y, widthDelta, newRect.height); |
| } finally { |
| gc.dispose(); |
| } |
| } |
| } |
| oldRect = newRect; |
| } |
| } |
| </programlisting> |
| </section> |
| <section id="sec-tab-traversal"> |
| <title>Tab Traversal</title> |
| <para> |
| Another concern of Swing/SWT integration is the behavior of the tab |
| key when traversing between components from different toolkits. |
| Additional work is needed on the AWT side of the SWT/AWT |
| bridge to solve this problem. To achieve proper tab traversal, implement a custom |
| subclass of <code>java.awt.FocusTraversalPolicy</code> for the embedded AWT frame. |
| The custom policy may delegate to a standard policy when |
| tabbing within the frame, but it must transfer control to an SWT |
| component when: |
| </para> |
| <orderedlist> |
| <listitem> |
| <simpara> |
| tabbing on the last component in the frame |
| </simpara> |
| </listitem> |
| <listitem> |
| <simpara> |
| back-tabbing on the first component in the frame |
| </simpara> |
| </listitem> |
| </orderedlist> |
| <para> |
| Implementing the forward and backward traversals in the custom policy is simple. |
| However, implementing a proper <code>getDefaultComponent</code> method is tricky. It |
| must return null when nothing in the embedded AWT frame has focus. This behavior is a hack based on |
| assumptions about how AWT will call the method, and it is necessary to avoid an immediate |
| transfer of focus back to AWT after traversing to SWT. For more information, refer to the comments |
| in the class <code>EmbeddedChildFocusTraversalPolicy</code> in <xref linkend="app-example-code"/>. |
| </para> |
| <para> |
| See the Swing documentation for the |
| <ulink url="http://java.sun.com/j2se/1.5.0/docs/api/java/awt/doc-files/FocusSpec.html">AWT Focus Subsystem</ulink> |
| for more information on focus traversal policies. |
| </para> |
| </section> |
| <section id="sec-modality"> |
| <title>Modal Dialogs</title> |
| <para> |
| When a |
| modal dialog is opened from a Swing component, it must be modal across the |
| entire application. Since SWT and AWT have separate event threads, |
| such dialogs are not modal by default. The SWT event thread must |
| be explicitly disabled during the time that a Swing modal dialog is showing. |
| This effect is most easily achieved by opening a modal SWT dialog while the |
| Swing dialog is showing. |
| </para> |
| <programlisting> |
| java.awt.Dialog awtDialog = ... |
| Shell shell = new Shell(parent, SWT.APPLICATION_MODAL | SWT.NO_TRIM); |
| shell.setSize(0, 0); |
| shell.addFocusListener(new FocusAdapter() { |
| public void focusGained(FocusEvent e) { |
| awtDialog.requestFocus(); |
| awtDialog.toFront(); |
| } |
| }); |
| </programlisting> |
| |
| <para> |
| The 0 x 0 size prevents the shell from being seen. The focus listener makes |
| sure that control is restored to the Swing dialog if the SWT window somehow gains focus. |
| (For example, the SWT window may gain focus when the user navigates to your application |
| through some window managers.) |
| </para> |
| <note> |
| <para> |
| On Linux/GTK, even a zero-sized SWT dialog may appear as a small dot |
| on the screen. Hide it by adding the following code to |
| <code>focusGained()</code>. This addition is unnecessary under Windows and |
| actually causes a flash, so make the call only when required. |
| </para> |
| <programlisting> |
| shell.moveBelow(null); |
| </programlisting> |
| </note> |
| <para> |
| The code shown above ensures correct modal behavior, but how should it be invoked? |
| The easiest approach is to invoke it wherever Swing modal dialogs are created. |
| However, this solution won't work if your Swing component library cannot be |
| modified, or if you cannot introduce dependencies on SWT code from |
| the Swing component library. |
| </para> |
| <para> |
| Alternatively, correct modal behavior is enforced more cleanly by installing a listener for all AWT |
| window events. Whenever it is detected that a Swing modal dialog is open or visible, a |
| SWT modal dialog must be opened. The listener is sketched below and is |
| fully implemented in the example code. See <xref linkend="app-example-code"/> for more |
| information. |
| </para> |
| <programlisting> |
| class AwtDialogListener implements AWTEventListener, ComponentListener { |
| |
| private final Display display; |
| |
| AwtDialogListener(Display display) { |
| this.display = display; |
| Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.WINDOW_EVENT_MASK); |
| } |
| |
| private void handleRemovedDialog(Dialog awtDialog, boolean removeListener) { |
| // An AWT dialog has been closed or set invisible. Close the SWT dialog |
| ... |
| } |
| |
| private void handleAddedDialog(final Dialog awtDialog) { |
| // An AWT dialog has been opened or set visible. Open the SWT dialog |
| // and add this as a component listener on the dialog to catch |
| // visibility changes |
| ... |
| } |
| |
| private void handleOpenedWindow(WindowEvent event) { |
| Window window = event.getWindow(); |
| if (window instanceof Dialog) { |
| handleAddedDialog((Dialog)window); |
| } |
| } |
| |
| private void handleClosedWindow(WindowEvent event) { |
| // Dispose-based close |
| Window window = event.getWindow(); |
| if (window instanceof Dialog) { |
| // Remove dialog and component listener |
| handleRemovedDialog((Dialog)window, true); |
| } |
| } |
| |
| private void handleClosingWindow(WindowEvent event) { |
| // System-based close |
| // (see example code for full implementation) |
| ... |
| } |
| |
| public void eventDispatched(AWTEvent event) { |
| switch (event.getID()) { |
| case WindowEvent.WINDOW_OPENED: |
| handleOpenedWindow((WindowEvent)event); |
| break; |
| |
| case WindowEvent.WINDOW_CLOSED: |
| handleClosedWindow((WindowEvent)event); |
| break; |
| |
| case WindowEvent.WINDOW_CLOSING: |
| handleClosingWindow((WindowEvent)event); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| public void componentHidden(ComponentEvent e) { |
| Object obj = e.getSource(); |
| if (obj instanceof Dialog) { |
| // Remove dialog but keep listener in place so that we know if/when it is set visible |
| handleRemovedDialog((Dialog)obj, false); |
| } |
| } |
| |
| public void componentShown(ComponentEvent e) { |
| Object obj = e.getSource(); |
| if (obj instanceof Dialog) { |
| handleAddedDialog((Dialog)obj); |
| } |
| } |
| ... |
| } |
| </programlisting> |
| </section> |
| <section id="sec-dismissing-popups"> |
| <title>Dismissing Pop-up Menus</title> |
| <para> |
| When a context menu is displayed in an embedded AWT frame, the menu does not |
| disappear after clicking outside the frame. This AWT limitation is well known, |
| but it is especially noticeable in embedded frames since it may result in |
| multiple visible pop-ups within the same SWT shell. |
| </para> |
| <figure id="fig-double-popup"> |
| <title>Double Popups</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/double-popup.jpg"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| <para> |
| This issue is documented in Sun bugs |
| <ulink url="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4311449">4311449</ulink> |
| and <ulink url="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4290715">4290715</ulink>, |
| among others. |
| </para> |
| <para>There are partial workarounds to this problem:</para> |
| <itemizedlist> |
| <listitem> |
| <para> |
| <emphasis>Manually dismiss Swing pop-ups when the user activates other windows.</emphasis> Install a |
| listener for window focus events on the embedded frame. On a focus lost event, the window |
| and component hierarchies of the frame can be searched for instances of |
| <code>javax.swing.JPopupMenu</code>. |
| <note> |
| <para> |
| This workaround applies to JRE version 1.4 and earlier; it should not be |
| necessary if you are using version 1.5 or later. |
| </para> |
| </note> |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| <emphasis>Manually dismiss Swing pop-ups when the user activates an SWT menu.</emphasis> Install |
| a display filter to listen for <code>SWT.Show</code> events which are generated whenever an |
| SWT menu is shown. In the filter's event handler, visible Swing pop-ups can be dismissed as described |
| above. |
| </para> |
| </listitem> |
| </itemizedlist> |
| <para> |
| Despite these workarounds, some issues remain; for example, Swing pop-ups are not |
| dismissed when the user interacts with the titlebar on the workbench window. |
| </para> |
| </section> |
| <section id="sec-system-settings"> |
| <title>Synchronizing with System Settings</title> |
| <para> |
| When the user changes font settings through the Windows control panel, the |
| changes are not always properly propagated to Swing components. This problem is |
| especially noticeable when integrating with SWT since the SWT components do, in fact, |
| recognize these changes. To work around the problem, manually |
| change the font in the Swing Windows Look and Feel when the system font has changed. |
| </para> |
| <para> |
| Fortunately, in Eclipse 3.2, a new SWT event has been added to allow applications |
| to detect changes to system settings. So, Swing font changes can be triggered |
| as follows |
| </para> |
| <programlisting> |
| Display display = ... |
| Listener settingsListener = new Listener() { |
| public void handleEvent(Event event) { |
| handleSettingsChange(); |
| } |
| }; |
| display.addListener(SWT.Settings, settingsListener); |
| </programlisting> |
| <para> |
| Font changes for Swing components are best handled through the |
| look and feel, rather than updating the font for individual components. |
| </para> |
| <programlisting> |
| private void handleSettingsChange() { |
| ... |
| org.eclipse.swt.graphics.Font currentSystemFont = ...; |
| FontData fontData = currentSystemFont.getFontData()[0]; |
| |
| int resolution = Toolkit.getDefaultToolkit().getScreenResolution(); |
| int awtFontSize = (int)Math.round((double)fontData.getHeight() * resolution / 72.0); |
| |
| // The style constants for SWT and AWT map exactly, and since they are int constants, they should |
| // never change. So, the SWT style is passed through as the AWT style. |
| java.awt.Font awtFont = new java.awt.Font(fontData.getName(), fontData.getStyle(), awtFontSize); |
| |
| FontUIResource fontResource = new FontUIResource(awtFont); |
| |
| UIManager.put("Button.font", fontResource); |
| UIManager.put("CheckBox.font", fontResource); |
| UIManager.put("ComboBox.font", fontResource); |
| ... // many more similar calls |
| |
| Container contentPane = ... // content pane from the root pane |
| SwingUtilities.updateComponentTreeUI(contentPane); |
| } |
| } |
| </programlisting> |
| <para> |
| First, the SWT font is converted to an equivalent AWT font. |
| AWT font sizes always assume a 72 dpi resolution. The true screen resolution must be |
| used to convert the platform font size into an AWT point size that matches when displayed. |
| Then, the font for various Swing component types is changed via the look and feel's |
| <code>javax.swing.UIManager</code>. |
| </para> |
| <para> |
| See the example code (<xref linkend="app-example-code"/>) for the complete implementation. |
| </para> |
| </section> |
| <section id="sec-keystroke-contention"> |
| <title>Keystroke Contention</title> |
| <para> |
| Unexpected results can occur if you map the same keystroke to a global action |
| in your RCP application and to a handler in an embedded Swing component. |
| Keystrokes defined through the <code><ulink url="http://help.eclipse.org/help32/index.jsp?topic=/org.eclipse.platform.doc.isv/reference/extension-points/org_eclipse_ui_bindings.html"> |
| org.eclipse.ui.bindings</ulink></code> extension point will take precedence over those |
| defined in the Swing component, even if the Swing component has focus. In this case, the |
| RCP binding is managed through a Display filter, so the keystroke is consumed before |
| it reaches the Swing component at all. As a result, there is no simple, general |
| workaround to avoid this problem at the level of the embedded composite. Instead, you |
| can avoid these conflicts by: |
| <itemizedlist> |
| <listitem> |
| <para> |
| Using the <code><ulink url="http://help.eclipse.org/help32/index.jsp?topic=/org.eclipse.platform.doc.isv/reference/extension-points/org_eclipse_ui_contexts.html"> |
| org.eclipse.ui.contexts</ulink></code> extension point to organize key bindings that should |
| not be visible when the Swing component is in focus. |
| </para> |
| </listitem> |
| <listitem> |
| <para> |
| Replicate the Swing component's action in an RCP action, bound to the same keystroke. The |
| binding for RCP action can be introduced through an <code>org.eclipse.ui.contexts</code> |
| extension. The action can be implemented to invoke the underlying the Swing action. This |
| approach may be necessary anyway, if you want the action to be available anywhere outside |
| the Swing component (for example, in the application's main menu). |
| </para> |
| </listitem> |
| </itemizedlist> |
| </para> |
| <para> |
| There are also occasional issues with keystroke contention between the embedded Swing |
| component and the window system. For example, hitting Shift-F10 from inside many Swing |
| components opens a context (popup) menu. In Windows, when there is no context menu, the default |
| processing of the F10 keystroke |
| (with or without shifting) transfers focus to the application's main menu bar. |
| When you have embedded Swing components, these two behaviors conflict. |
| The Swing component properly handles Shift-F10, showing the popup, but the containing SWT shell does |
| not know it; therefore, it reports the keystroke to Windows as unhandled. Windows then |
| transfers focus to the main menu bar, and the popup loses focus. |
| </para> |
| <para> |
| Fortunately, this conflict can be resolved. Here's some code which exploits the fact that |
| the default Windows behavior happens when F10 is released, rather than when it is pressed. |
| The embedded composite can install a KeyListener and consume the release of the |
| Shift-F10 keystroke so that Windows never sees it. |
| </para> |
| <programlisting> |
| public void keyReleased(KeyEvent e) { |
| if (e.keyCode == SWT.F10 && (e.stateMask & SWT.SHIFT) != 0) { |
| e.doit = false; |
| } |
| } |
| </programlisting> |
| </section> |
| <section id="sec-other-workarounds"> |
| <title>Other Workarounds</title> |
| <para> |
| Due to Sun bug <ulink url="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6411042">6411042</ulink>, |
| when AWT/Swing components are |
| added to an embedded frame from the SWT event thread, a memory leak occurs. To |
| avoid the leak, it is best to create and add these components from the AWT event thread |
| instead. Simply invoke the creation code through |
| <code>javax.swing.SwingUtilities.invokeLater()</code>. |
| As discussed earlier, it is a good general habit to call Swing APIs from the |
| AWT event thread anyway. |
| </para> |
| </section> |
| </section> |
| <section id="sec-unresolved-issues"> |
| <title>Unresolved Issues</title> |
| <section id="sec-cleartype"> |
| <title>ClearType</title> |
| <para> |
| On Windows under JRE 1.5 and earlier, the Swing system look and feel does not |
| seamlessly support <ulink url="http://www.microsoft.com/typography/ClearTypeInfo.mspx">ClearType</ulink> |
| anti-aliasing. By default, when ClearType is enabled and the same font and font |
| size are chosen for Swing and SWT controls, the fonts appear to be different. A |
| partial solution is available through setting the |
| property <code>swing.aatext</code> to "true". This undocumented property enables font anti-aliasing |
| in pre-Java 6 environments, but it simply turns anti-aliasing on. It does not synchronize with current |
| Windows system settings. Also, Swing has its own anti-aliasing algorithm, so |
| minor differences from natively-displayed fonts remain visible. |
| </para> |
| <para> |
| This problem has been <ulink url="http://weblogs.java.net/blog/chet/archive/2005/06/phils_font_fixe.html">resolved</ulink> |
| in Java 6 (see bugs <ulink url="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4726365">4726365</ulink> |
| and <ulink url="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4871297">4871297</ulink> |
| for more information). |
| </para> |
| <para> |
| Below is a screenshot which illustrates the problem. Here, two SWT-based views are on |
| the left, and a Swing table component is on the right. |
| </para> |
| <figure id="fig-cleartype"> |
| <title>Font Differences with ClearType</title> |
| <mediaobject> |
| <imageobject> |
| <imagedata fileref="images/cleartype.jpg"/> |
| </imageobject> |
| </mediaobject> |
| </figure> |
| </section> |
| <section id="sec-cursor-synchronization"> |
| <title>Cursor Synchronization</title> |
| <para> |
| SWT cursor changes are not reflected in embedded Swing components. The SWT cursor |
| changes back to the default arrow cursor as the mouse moves over the embedded frame. |
| </para> |
| <para> |
| A solution to this problem may now be possible. As of Eclipse 3.3, new API is |
| available to query the current cursor (see bug |
| <ulink url="https://bugs.eclipse.org/bugs/show_bug.cgi?id=133943">133943</ulink> |
| ). |
| </para> |
| </section> |
| </section> |
| <section id="sec-conclusion"> |
| <title>Conclusion</title> |
| <para> |
| Smooth integration of Swing components into an RCP application is possible. It requires |
| more than the SWT/AWT Bridge, however. You can implement the techniques described above |
| in a modular fashion to improve the behavior of the SWT/AWT Bridge for |
| real-world applications. <xref linkend="app-example-code"/> contains example |
| code that demonstrates all of these additional practices. |
| </para> |
| </section> |
| |
| <bibliography id="bin-resources"> |
| <title>Resources</title> |
| |
| <biblioentry id="bib-example-code"> |
| <bibliosource><ulink url="files/swingintegration.example_0.0.2.jar">Example Code</ulink></bibliosource> |
| </biblioentry> |
| |
| <biblioentry id="bib-bridge"> |
| <bibliosource> |
| <ulink |
| url="http://help.eclipse.org/help32/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/awt/package-summary.html"> |
| SWT/AWT Bridge Javadoc |
| </ulink> |
| </bibliosource> |
| </biblioentry> |
| <biblioentry id="bib-look-and-feel"> |
| <bibliosource> |
| <ulink |
| url="http://java.sun.com/docs/books/tutorial/ui/features/plaf.html"> |
| Java Look and Feel Information |
| </ulink> |
| |
| </bibliosource> |
| </biblioentry> |
| <biblioentry id="bib-root-pane"> |
| <bibliosource> |
| <ulink |
| url="http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JRootPane.html"> |
| Swing Root Pane Documentation |
| </ulink> |
| |
| </bibliosource> |
| </biblioentry> |
| <biblioentry id="bib-awt-focus"> |
| <bibliosource> |
| <ulink url="http://java.sun.com/j2se/1.5.0/docs/api/java/awt/doc-files/FocusSpec.html">AWT Focus Subsystem</ulink> |
| </bibliosource> |
| </biblioentry> |
| </bibliography> |
| |
| <appendix id="app-example-code"> |
| <title>Example Code</title> |
| <para> |
| The ideas for improving Swing/SWT integration presented in |
| <xref linkend="sec-adding-to-bridge"/> have been collected into a full |
| example implementation <xref linkend="bib-example-code"/>. The example |
| includes an API that is used to create embedded Swing components |
| simply and cleanly. The example code requires SWT version 3.2 or higher. |
| </para> |
| <para> |
| Import the example with "File -> Import...", selecting "Plug-ins and Fragments". Import the |
| plug-in as "Projects with Source Folders". Make sure that your |
| compiler settings specify a source compatibility version of at least 1.4 (Preferences -> Java -> Compiler). |
| Otherwise the <code>assert</code> statements in the example code will not compile. |
| </para> |
| <para> |
| <ulink url="files/example-javadoc/index.html">Javadoc</ulink> |
| is available for the API. To embed Swing components, refer to |
| the javadoc for |
| <code> |
| <ulink |
| url="files/example-javadoc/swingintegration/example/EmbeddedSwingComposite.html"> |
| EmbeddedSwingComposite</ulink></code>. To display Swing dialogs without embedding, refer to the javadoc for |
| <code> |
| <ulink url="files/example-javadoc/swingintegration/example/AwtEnvironment.html"> |
| AwtEnvironment</ulink></code>. |
| </para> |
| </appendix> |
| </article> |
| |