MacOS JInternalFrame border problem

Post to Twitter

With new MacOS versions, the good old Aqua LaF has some problems.

If you create a simple MDI application with JDesktopPane and JInternalFrame, following problem will occur:

Default JInternalFrame

Default JInternalFrame

The code for our problem:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JTabbedPane;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.plaf.UIResource;

public class TestJVxTabbedPane
{
    public static void main(String[] args) throws Exception
    {
        new TestJVxTabbedPane();
    }
   
    public TestJVxTabbedPane()
    {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new BorderLayout());
       
        JDesktopPane dpan = new JDesktopPane();
        dpan.setBackground(Color.white);
               
        JInternalFrame fr = new JInternalFrame();
        fr.setTitle("Super Frame");
        fr.setPreferredSize(new Dimension(200, 200));
        fr.pack();
        fr.setResizable(true);
        fr.setMaximizable(true);
        fr.setIconifiable(true);
        fr.setClosable(true);
        fr.setVisible(true);
               
        dpan.add(fr);
       
        frame.getContentPane().add(dpan, BorderLayout.CENTER);
        frame.setSize(500, 400);
        frame.setLocationRelativeTo(null);

        frame.setVisible(true);      
    }
}

(nothing special, a simple JInternalFrame, JDesktopPane combination)

We don't like bad looking UIs, so we fixed the problem in JVx:

JInternalFrame without border problem

JInternalFrame without border problem

The fix will work without JVx as well. We made tests with different MacOS versions and our solution worked in all our tests.

JVx Reference, Launchers and Applications

Post to Twitter

Let's talk about Launchers, and how they are used to start JVx applications.

Starting an application

We've previously outlined a simple way to start a JVx application, and now we're going to look at how to do it with a launcher to benefit from everything JVx has to offer. From a technical point of view, there are two prerequisites which must be fulfilled before a JVx application can run:

  1. the JVM must have started.
  2. the technology specific system must have started.

Then, and only then, the JVx application can run. Depending on the implementation that is used, that can be as easily as instancing the factory (Swing, JavaFX), but can also mean that a servlet server has to start (Vaadin). Because we do not wish to encumber our applications with technology specific code, we have to entrust all this to an encapsulated entity, meaning the implementations of ILauncher and IApplication.

Following the chain

The steps for getting an application to start are as follows:

  1. The first thing that must run is obviously the JVM, without it we won't have much luck starting anything.
  2. The launcher must be created and it must start the Technology.
  3. The launcher than creates the application which the user is seeing.

Launcher chain

So we need two classes, the ILauncher implementation which knows how to start the Technology and the IApplication implementation. That we already knew, so let's try to put this into code. For simplicity reasons (and because I don't want to write a complete factory from scratch for this example) we will reuse the Swing implementation and write a new launcher and application for it.

Entry point

The Main class that we will use as example is very straightforward:

  1. public class Main
  2. {
  3.     public static void main(String[] pArgs)
  4.     {
  5.         // All we have to do here is kickoff the creation of the launcher.
  6.         // The launcher will do everything that is required to start for us.
  7.         //
  8.         // In a real world scenario and/or application there might be more
  9.         // setup or groundwork required, for example processing the arguments,
  10.         // but we don't need any of that here.
  11.         new SwingLauncher();
  12.     }
  13. }

All we have to do there is start the launcher itself. As the comment suggests, there might be work required for a "real" application startup. For this example, it is all we need to do. Of course we could also directly embed this little function into the launcher implementation itself, to save us one class.

The launcher

The ILauncher implementation on the other hand contains quite some logic, but nothing not manageable:

  1. public class SwingLauncher extends SwingFrame
  2.                            implements ILauncher
  3. {
  4.     // We have to extend from SwingFrame because there is no factory
  5.     // instantiated at that point, so we can't use UI components.
  6.    
  7.     private IApplication application;
  8.    
  9.     public SwingLauncher()
  10.     {
  11.         super();
  12.        
  13.         try
  14.         {
  15.             SwingUtilities.invokeAndWait(this::startup);
  16.         }
  17.         catch (InvocationTargetException | InterruptedException e)
  18.         {
  19.             e.printStackTrace();
  20.         }
  21.     }
  22.  
  23.     @Override
  24.     public void dispose()
  25.     {
  26.         try
  27.         {
  28.             // We must notify the application that we are being disposed.
  29.             application.notifyDestroy();
  30.         }
  31.         catch (SecurityException e)
  32.         {
  33.             e.printStackTrace();
  34.         }
  35.        
  36.         super.dispose();
  37.        
  38.         // We have to make sure that the application is exiting when
  39.         // the frame is disposed of.
  40.         System.exit(0);
  41.     }
  42.  
  43.     private void startup()
  44.     {
  45.         // We create a new SwingFactory and it is directly registered as global
  46.         // instance, that means it will be used by all components which are
  47.         // created from now on.
  48.         UIFactoryManager.getFactoryInstance(SwingFactory.class);
  49.        
  50.         // Also we set it as our factory instance.
  51.         setFactory(UIFactoryManager.getFactory());
  52.        
  53.         // Because the IApplication implementation we use is based upon
  54.         // UI components (which is advisable) we have to wrap this launcher
  55.         // in an UILauncher.
  56.         UILauncher uiLauncher = new UILauncher(this);
  57.        
  58.         // Now we create the main application.
  59.         // Note that the ExampleApplication is already based upon
  60.         // UI components.
  61.         application = new ExampleApplication(uiLauncher);
  62.        
  63.         // Then we add the application as content to the launcher.
  64.         uiLauncher.add(application);
  65.        
  66.         // Perform some setup work and start everything.
  67.         uiLauncher.pack();
  68.         uiLauncher.setVisible(true);
  69.        
  70.         // We also have to notify the application itself.
  71.         application.notifyVisible();
  72.     }
  73.    
  74.     // SNIP
  75. }

In short, the launcher is kicking off the Swing thread by invoking the startup method on the main Swing thread. This startup method will instantiate the factory and then create the application. From there we only need to set it visible and then our application has started.

The launcher extends from SwingFrame, that is required because there hasn't been a factory created yet which could be used by UI components to create themselves. If we'd try to use an UI component before creating/setting a factory, we would obviously see the constructor of the component fail with a NullPointerException.

The method startup() is invoked on the main Swing thread, which also happens to be the main UI thread for JVx in this application. Once we are on the main UI thread we can create the application, add it and then set everything to visible.

The application

The IApplication implementation is quite short, because we extend com.sibvisions.rad.application.Application, an IApplication implementation created with UI components.

  1. public class ExampleApplication extends Application
  2. {
  3.     public ExampleApplication(UILauncher pParamUILauncher)
  4.     {
  5.         super(pParamUILauncher);
  6.     }
  7.    
  8.     @Override
  9.     protected IConnection createConnection() throws Exception
  10.     {
  11.         // Not required for this example.
  12.         return null;
  13.     }
  14.    
  15.     @Override
  16.     protected String getApplicationName()
  17.     {
  18.         return "Example Application";
  19.     }
  20. }

Because the launcher has previously started the technology and created the factory we can from here on now use UI components, which means we are already independent of the underlying technology. So the IApplication implementation can already be used with different technologies and is completely independent.

Notes on the launcher

As you might have noticed, in our example the launcher is a (window) frame, that makes sense for nearly every desktop GUI toolkit as they all depend upon a window as main method to display their applications. But the launcher could also be simpler, for example just a call to start the GUI thread. Or it could be something completely different, for example an incoming HTTP request.

Also don't forget that the launcher is providing additional functionality to the application, like saving file handles, reading and writing the configuration and similar platform and toolkit dependent operations, see the launcher for Swing for further details.

Conclusion

This example demonstrates how a simple launcher is implemented and why it is necessary to have a launcher in the first place. Compared with the "just set the factory" method this seems like a lot of work, but the launchers used by JVx are of course a lot more complex than these examples, that is because they implement all the required functionality and also take care of a lot of boiler plate operations. It is taking care of all technology specific code and allows to keep your application free from knowing about the platform it runs on.

VisionX 2.4 u2 is out

Post to Twitter

We're happy to announce VisionX 2.4 Update Release 2. It's another milestone for us.

The new version is a bugfix release with some important features for all our customers. The top bug was a missing license check for our HTML5 implementation. With older versions it was possible to use our HTML5 application without valid license. This wasn't a hidden feature, it was a real bug. The new version has a clean license check and some of you will need a new license. This is no problem, simply contact us.

What's new?

  • Full-Screen mode

    Press F12 to maximize VisionX without frame border.

  • HTML5 Live preview options

    It supports "application per session", custom UI factory, custom Application setups, main, config and externalCss URL parameter.

  • VisionX is now http session ready

    It will be possible to use VisionX on server-side as backend for remote application development.

  • Fixed Copy/Paste for DnD fields

    It's now possible to Copy/Paste into field with DnD support.

  • Automatic UI tests

    VisionX now supports automatic UI tests with an optional AddOn. The mechanism isn't limited to our AddOn. It's also possible to create custom Modules/AddOns.

  • Tibero Database support

    VisionX has built-in support for Tibero Database (tested with version 6).

  • Auto-restart feature

    VisionX got a Preloader. This allows custom libraries without manually changing the classpath. The old classpath mechanism is still available, but we use the Preloader by default.

  • Action Editor with custom editor support

    It's now possible to add custom editors in the action editor via action.xml and/or a custom module.

  • GridLayout support

    It's now possible to use panels with GridLayout, if set in Source Code.

  • Custom vaadin widgetsets and themes

    It's now possible to create custom vaadin widgetsets, e.g. add an AddOn from the Vaadin Directory. It's super easy to create a custom theme and setting your custom css attributes.

  • Performance improvement

    We reduced the fetch calls during screen creation. This will improve performance with big tables and complex views.

  • New modules, AddOns and demo applications (optional)

    The VaadinAddon can be used to create a custom widgetset or theme. The AppTeste is an automatic UI test tool. There is a new demo application for testing Validators. The new reporting demo application will show all features of our reporting engine.

  • Configurable EPlug ports

    It's now possible to configure the EPlug communication ports via system properties.

  • Better Application signing

    Our signing mechanism now supports TSA Urls, Proxies, signing algorithms and much more.

  • Service module merging

    Newly created applications will automatically merge service modules. This is an important bugfix for e.g. the profiles module.

The new version is available in your download area or as Trial version.