Posts tagged: JavaFX

JavaFX FlowPane vs. FXFluidFlowPane

Post to Twitter

We're still working on our JavaFX UI implementation for JVx. Some weeks ago, we worked with standard JavaFX layouts (layout panes) like BorderPane and FlowPane. Both layouts are useful but don't work like BorderLayout or FlowLayout from AWT/Swing. There are small differences, e.g.

It was possible to resize a BorderLayout to any size. The BorderPane checks minimum and maximum size of its nodes and doesn't resize if bounds were reached. Sure, that's useful in theory but bad in practice because the content of a screen should always resize to the screen size (e.g internal frames).
The requirement wasn't hard to implement. We now have our own FXBorderPane which has its own min. and max. calculation.

The standard BorderPane was very useful but the standard FlowPane wasn't, because it has bigger problems/limitations:

  • Overlapping of Wrapped FlowPanes with other nodes
    Overlapping

    Overlapping

  • Size calculation depends on prefWrapLength if not stretched to full-size (BorderPane, SplitPane, ...). This means that the pane doesn't grow automatically if the parent has enough space.
    ABC

    Width calculation

  • The FlowPane doesn't support alignment of managed nodes
    Standard FlowPane (centered nodes)

    Standard FlowPane (centered nodes)

    but should:

    fluid flow pane (bottom aligned)

    Fluid flow pane (bottom aligned)

    fluid flow pane (stretched)

    Fluid flow pane (stretched)

We solved all problems with our FXFluidFlowPane because our applications won't work with standard FlowPane.

In JVx applications, we have more than two layouts. The most common layout is our FormLayout. We already have JavaFX implementations for all JVx layouts, like FXFormPane or FXNullPane.

Here's screenshot of our FXFormPane test application:

Form Pane

Form Pane

JavaFX, JVx, CalendarFX and Exchange Server

Post to Twitter

It's friday, and it's (still) sunny :)

Some days ago, CalendarFX was released. It's a commercial product and looks promising. Now and then, we play around with new commercial products/libraries because our dev teams should know which product will work in commercial projects. A calendar control is always useful and especially if you organize "something". Many ERP products do this.

In good old swing applications, we did use migcalendar for better UX and visualization. But it's not available for JavaFX, so we tried CalendarFX.

The control is still in an early development stage and has some bugs or missing APIs, but it's very polished and works great with JVx and our JavaFX UI:

JVx, JavaFX UI and CalendarFX

JVx, JavaFX UI and CalendarFX

We tried to implement a simple JavaFX calendar screen, for Outlook appointments. We already had a connector for Exchange servers, based on EWS Java API and our JVx' storage implementation.

The screen code was simple, and more or less a simply copy/paste of a CalendarFX tutorial application. Here it is:

private void initializeUI() throws ModelException
    {
        CalendarView calendarView = new CalendarView();
       
        Calendar work = new Calendar("Work");
        Calendar home = new Calendar("Home");

        work.setStyle(Style.STYLE1);
        home.setStyle(Style.STYLE2);

        CalendarSource calSources = new CalendarSource("Private");
        calSources.getCalendars().addAll(work, home);

        calendarView.getCalendarSources().addAll(calSources);

        calendarView.setRequestedTime(LocalTime.now());

        Thread updateTimeThread = new Thread("Calendar: Update Time Thread")
        {
            @Override
            public void run()
            {
                while (true)
                {
                    Platform.runLater(() ->
                    {
                        calendarView.setToday(LocalDate.now());
                        calendarView.setTime(LocalTime.now());
                    });

                    try
                    {
                        // update every 10 seconds
                        sleep(10000);
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }

                }
            };
        };

        Thread thLoadData = new Thread(new Runnable()
        {
            public void run()
            {
                try
                {
                    RemoteDataBook rdb = new RemoteDataBook(apps);
                    rdb.setDataSource(getDataSource());
                    rdb.setName("calendar");
                    rdb.open();
                    rdb.fetchAll();
                   
                    ZonedDateTime zdt;
                   
                    for (int i = 0; i < rdb.getRowCount(); i++)
                    {
                        IDataRow row = rdb.getDataRow(i);
                       
                        Entry entry = new Entry(row.getValueAsString("SUBJECT"));
                       
                        zdt = ((Date)row.getValue("BEGIN")).toInstant().
                                     atZone(ZoneId.systemDefault());
                       
                        entry.setStartDate(zdt.toLocalDate());
                        entry.setStartTime(zdt.toLocalTime());
                       
                        if (((Boolean)row.getValue("ALLDAY")))
                        {
                            entry.setFullDay(true);
                            entry.setEndDate(entry.getStartDate());
                        }
                        else
                        {
                            zdt = ((Date)row.getValue("END")).toInstant().
                                         atZone(ZoneId.systemDefault());                                                        
                            entry.setEndDate(zdt.toLocalDate());
                            entry.setEndTime(zdt.toLocalTime());
                        }
                       
                        if (((Boolean)row.getValue("PRIVATE")))
                        {
                            Platform.runLater(() -> home.addEntry(entry));
                        }
                        else
                        {
                            Platform.runLater(() -> work.addEntry(entry));
                        }
                    }
                }      
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
        });
       
        thLoadData.start();
        updateTimeThread.start();
       
        ((FXInternalWindow)getResource()).setContent(calendarView);
       
        setTitle("Calendar FX Demo");
    }

The storage definition for fetching appointments was trivial:

AppointmentsStorage apps = new AppointmentsStorage();
apps.setURL(new URI("<url>"));
apps.setUserName("<username>");
apps.setPassword("<password>");
apps.open();

Not more code!

The special thing in our screen was the integration of a custom control. Usually, we would integrate it like this:

add(new UICustomComponent(calendarView));

but our custom component integration for JavaFX UI wasn't ready. No problem, because it's always possible to access the real UI resource in JVx. So we did:

((FXInternalWindow)getResource()).setContent(calendarView);

and everything was as expected. Sure, the screen won't work with Swing because we made a direct access to a JavaFX resource, but this was not relevant for our test :)

JavaFX: Styled stage and MDI system

Post to Twitter

I'm happy to show you a first screenshot of our new Stage style and our MDI system:

Scene styling and MDI

Scene style and MDI

Compared to the standard stage:

Standard scene and MDI

Standard scene and MDI

Sure, the default stage is OK, but if you want to style the whole application, it won't work with standard stage. If you want a unique style, you need a custom solution. Our style is part of our JavaFX UI for JVx and already available in our repository at sourceforge.

The MDI system is already stable. It can be styled via css and works similar to JDesktopPane and JInternalFrame of Swing. Most problems were solved and we use the implementation in our dev projects.

Drag'n'Drop support for JavaFX' TabPane

Post to Twitter

We're currently working on our JavaFX UI for JVx. We're making good progress but sometimes, missing JavaFX functionality stops us, e.g. We need a Tab Pane with Drag and Drop support for Tabs. There's no support in current Java 8 versions :(

But Tom Schindl had the same requirement for his e(fx)clipse project. We thought that his implementation could be a simple solution for us as well!

But nothing is soo easy.

Our first problem was that we didn't need the whole project because it has "some dependencies" and consists of different jar files. So we thought that it would be a good idea to use only the relevant classes. Not so simple because of the license (EPL). The source code integration in our project wasn't possible, because we're Apache 2.0 licensed. But it was allowed to use some classes, create a new project with EPL and add the library to our project (thanks Tom for sharing your thoughts with us).
The result is our javafx.DndTabPane project, hosted on github.

The implementation of Tom worked great, but we had some extra requirements for our API:

  • Don't drag disabled tabs
  • It should be possible to disable dragging
  • Event if dragging

Sure, we made some smaller changes to allow customization.

In addition to Drag and Drop support, we found two problems with standard TabPane: RT-40149 and RT-40150.

We have one workaround and one dirty fix for the problems in our UI implementation: RT-40149 and RT-40150. (Not proud of it, but works)

A DesktopPane for JavaFX

Post to Twitter

If you plan to create desktop business applications, you'll miss one thing in JavaFX: A real desktop pane with internal frames.

The only comparable thing is the Window implementation of JFXtras. The implementation was a first step in the right direction, but not a real desktop/internal frame solution.

We did some experiments with Window and had following problems:

  • Mouse cursor was changed to resize but not back to default
  • Resize operation moved the window to totally different positions
  • It was possible to drag the window out of the parent node
  • Components were set to managed (false) -> a lot of layouting problems
  • Missing maximize functionality
  • Problems with focus handling
  • Title wasn't always shown
  • No tab/window mode switching option
  • Modality?

It was not a real internal frame, as we knew it from Swing. And a desktop pane/window manager wasn't available, which could be used to manage active/inactive windows/frames.

Our customers won't work without MDI because they need more than one frame, because of different reasons. Sure it depends on the application and use case but big applications without internal frames are a No-Go.

So we thought that it's the right time to start with a real desktop implementation with internal frames. Here are some imporessions:

Desktop + InternalFrames

Desktop + InternalFrames

 
Maximized frame

Maximized frame

Desktop with Tabs

Desktop with Tabs

We need better styles for the frames and some nice icons, but this isn't a big problem because we use stylesheets for everything. The implementation isn't production ready at the moment, but we solved most problems and made good progress.

The current desktop/frames knew the difference between active and inactive, it possible to minimize/iconify frames, focus handling works, maximization is possible and it's possible to switch between frame and tab mode. Dragging works like a charm.

Our implementation will be a real replacement for Swing' internal frames. It's part of our JavaFX UI implementation for JVx.

Simple DB application with JavaFX

Post to Twitter

In our last article, we told you that we're working on a JavaFX UI implementation for JVx. It's getting better every day and we think it's time to show you some source code :)

We use a very simple application for database access tests, it's this one:

javafx_DBaccess

It's a simple screen that shows a table of contacts with Search/Insert/Delete/Edit/Export options. There's a detail form that shows an image and all available information as editors. The editors were bound to the table and are always in sync.
You can change the value of an editor and the table will be updated automatically. The editors will be updated, if you change a cell in the table.

The Oracle database tables were created with following statements:

CREATE TABLE ACADEMICTITLES
(
  id             NUMBER(16) NOT NULL,
  academic_title VARCHAR2(200) NOT NULL
);
ALTER TABLE ACADEMICTITLES
  ADD PRIMARY KEY (ID);
ALTER TABLE ACADEMICTITLES
  ADD constraint ACTI_UK UNIQUE (ACADEMIC_TITLE);
CREATE TABLE COUNTRIES
(
  id      NUMBER(16) NOT NULL,
  country VARCHAR2(200) NOT NULL,
  eu      CHAR(1) DEFAULT 'N' NOT NULL
);
ALTER TABLE COUNTRIES
  ADD PRIMARY KEY (ID);
ALTER TABLE COUNTRIES
  ADD constraint CTRY_UK UNIQUE (COUNTRY);
CREATE TABLE HEALTHINSURANCES
(
  id               NUMBER(16) NOT NULL,
  health_insurance VARCHAR2(200) NOT NULL
);
ALTER TABLE HEALTHINSURANCES
  ADD PRIMARY KEY (ID);
ALTER TABLE HEALTHINSURANCES
  ADD constraint HEIN_UK UNIQUE (HEALTH_INSURANCE);
CREATE TABLE SALUTATIONS
(
  id         NUMBER(16) NOT NULL,
  salutation VARCHAR2(200)
)
ALTER TABLE SALUTATIONS
  ADD PRIMARY KEY (ID);
ALTER TABLE SALUTATIONS
  ADD constraint SALU_UK UNIQUE (SALUTATION);
CREATE TABLE CONTACTS
(
  id          NUMBER(16) NOT NULL,
  salu_id     NUMBER(16),
  acti_id     NUMBER(16),
  firstname   VARCHAR2(200) NOT NULL,
  lastname    VARCHAR2(200) NOT NULL,
  street      VARCHAR2(200),
  nr          VARCHAR2(200),
  zip         VARCHAR2(4),
  town        VARCHAR2(200),
  ctry_id     NUMBER(16),
  birthday    DATE,
  hein_id     NUMBER(16),
  filename    VARCHAR2(200),
  image       BLOB
);
ALTER TABLE CONTACTS
  ADD PRIMARY KEY (ID);
ALTER TABLE CONTACTS
  ADD constraint CONT_ACTI_ID_FK FOREIGN KEY (ACTI_ID)
  REFERENCES ACADEMICTITLES (ID);
ALTER TABLE CONTACTS
  ADD constraint CONT_CTRY_ID_FK FOREIGN KEY (CTRY_ID)
  REFERENCES COUNTRIES (ID);
ALTER TABLE CONTACTS
  ADD constraint CONT_HEIN_ID_FK FOREIGN KEY (HEIN_ID)
  REFERENCES HEALTHINSURANCES (ID);
ALTER TABLE CONTACTS
  ADD constraint CONT_SALU_ID_FK FOREIGN KEY (SALU_ID)
  REFERENCES SALUTATIONS (ID);

CREATE sequence SEQ_CONTACTS_ID
minvalue 1
maxvalue 9999999999999999
start WITH 1
increment BY 1
nocache
ORDER;
/

CREATE OR REPLACE TRIGGER TR_CONTACTS_BR_I
  before INSERT ON CONTACTS
  FOR each row
begin

  IF :new.id IS NULL then
    SELECT seq_contacts_id.NEXTVAL INTO :new.id FROM dual;
  end IF;

end;
/

You can see that the table definition contains foreign keys for every selection (like salutations or academic titles). It's a clean relational database model. All foreign keys are combobox editors editors in our GUI:

FK Editors

FK Editors

The birthday field is a date editor:

Date editor

Date editor

Here's the full source code of the application and I'll explain all relevant parts in detail:

package com.sibvisions.apps.javafx.db;

import javax.rad.application.IContent;
import javax.rad.application.genui.Application;
import javax.rad.application.genui.UILauncher;
import javax.rad.genui.celleditor.UIDateCellEditor;
import javax.rad.genui.celleditor.UIImageViewer;
import javax.rad.genui.celleditor.UINumberCellEditor;
import javax.rad.genui.component.UILabel;
import javax.rad.genui.container.UIGroupPanel;
import javax.rad.genui.container.UIPanel;
import javax.rad.genui.container.UISplitPanel;
import javax.rad.genui.control.UIEditor;
import javax.rad.genui.layout.UIBorderLayout;
import javax.rad.genui.layout.UIFormLayout;
import javax.rad.model.ModelException;
import javax.rad.ui.IContainer;
import javax.rad.util.event.IExceptionListener;

import com.sibvisions.apps.components.FilterEditor;
import com.sibvisions.apps.components.NavigationTable;
import com.sibvisions.rad.persist.StorageDataBook;
import com.sibvisions.rad.persist.jdbc.DBAccess;
import com.sibvisions.rad.persist.jdbc.DBStorage;

public class SimpleDBApplication extends Application
                                 implements IExceptionListener
{
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Class members
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    private static final String            NO_IMAGE    =
                       "/com/sibvisions/apps/vxdemo/images/nobody.gif";
    private static final UIImageViewer    IMAGE_VIEWER =
                       new UIImageViewer(NO_IMAGE);

    private StorageDataBook sdbContacts;
    private UIBorderLayout  blThis             = new UIBorderLayout();
    private UISplitPanel    splitMain          = new UISplitPanel();
    private NavigationTable navContacts        = new NavigationTable();

    private UIFormLayout    flDetails          = new UIFormLayout();
    private UIFormLayout    layoutDetails      = new UIFormLayout();
    private UIPanel         panDetails         = new UIPanel();
    private UIGroupPanel    gpanDedails        = new UIGroupPanel("Contact");
    private UIBorderLayout  blContacts         = new UIBorderLayout();
    private UIPanel         panContacts        = new UIPanel();
    private UIFormLayout    layoutSearch       = new UIFormLayout();
    private UIPanel         panSearch          = new UIPanel();
    private UILabel         lblSalutation      = new UILabel("Salutation");
    private UILabel         lblAcademicTitle   = new UILabel("Academic title");
    private UILabel         lblFirstName       = new UILabel("First name");
    private UILabel         lblLastName        = new UILabel("Last name");
    private UILabel         lblStreet          = new UILabel("Street");
    private UILabel         lblNr              = new UILabel("Nr");
    private UILabel         lblZip             = new UILabel("ZIP");
    private UILabel         lblTown            = new UILabel("Town");
    private UILabel         lblBirthday        = new UILabel("DoB");
    private UILabel         lblHealthInsurance = new UILabel("Health insurance");
    private UILabel         lblSearch          = new UILabel("Search");
    private UIEditor        edtSalutation      = new UIEditor();
    private UIEditor        edtAcademicTitle   = new UIEditor();
    private UIEditor        edtFirstName       = new UIEditor();
    private UIEditor        edtLastName        = new UIEditor();
    private UIEditor        edtStreet          = new UIEditor();
    private UIEditor        editNr             = new UIEditor();
    private UIEditor        edtZip             = new UIEditor();
    private UIEditor        edtTown            = new UIEditor();
    private UIEditor        edtCountry         = new UIEditor();
    private UIEditor        edtBirthday        = new UIEditor();
    private UIEditor        edtHealthInsurance = new UIEditor();
    private FilterEditor    edtSearch          = new FilterEditor();
    private UIEditor        icoImage           = new UIEditor();
   
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Initialization
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /**
     * Creates a new instance of <code>Showcase</code>.
     *
     * @param pLauncher the launcher
     * @throws Throwable if the initialization failed
     */

    public SimpleDBApplication(UILauncher pLauncher) throws Throwable
    {
        super(pLauncher);
       
        setName("Simple DB application");
       
        initModel();
        initUI();
    }

    /**
     * Initializes model.
     *
     * @throws ModelException
     */

    private void initModel() throws ModelException
    {
        DBAccess dba = DBAccess.getDBAccess("jdbc:oracle:thin:@localhost:1521:xe");
        dba.setUsername("vxdemo");
        dba.setPassword("vxdemo");
        dba.open();
       
        DBStorage dbsContacts = new DBStorage();
        dbsContacts.setDBAccess(dba);
        dbsContacts.setWritebackTable("CONTACTS");
        dbsContacts.open();
       
        sdbContacts = new StorageDataBook(dbsContacts);
        sdbContacts.open();
       
        sdbContacts.getRowDefinition().getColumnDefinition("IMAGE").
             getDataType().setCellEditor(IMAGE_VIEWER);

        sdbContacts.getRowDefinition().getColumnDefinition("SOCIALSECNR").
             getDataType().setCellEditor(new UINumberCellEditor("0000"));
        sdbContacts.getRowDefinition().getColumnDefinition("BIRTHDAY").
             getDataType().setCellEditor(new UIDateCellEditor("dd.MM.yyyy"));
    }

    /**
     * Initializes UI.
     *
     * @throws Throwable if the initialization failed
     */

    private void initUI() throws Throwable
    {
        navContacts.setDataBook(sdbContacts);
        navContacts.setAutoResize(false);

        icoImage.setDataRow(sdbContacts);
        icoImage.setColumnName("IMAGE");
        icoImage.setPreferredSize(200, 140);

        edtSearch.setDataRow(sdbContacts);
        edtSalutation.setDataRow(sdbContacts);
        edtSalutation.setColumnName("SALU_SALUTATION");
        edtAcademicTitle.setDataRow(sdbContacts);
        edtAcademicTitle.setColumnName("ACTI_ACADEMIC_TITLE");
        edtFirstName.setDataRow(sdbContacts);
        edtFirstName.setColumnName("FIRSTNAME");
        edtLastName.setDataRow(sdbContacts);
        edtLastName.setColumnName("LASTNAME");
        edtStreet.setDataRow(sdbContacts);
        edtStreet.setColumnName("STREET");
        editNr.setDataRow(sdbContacts);
        editNr.setColumnName("NR");
        edtZip.setDataRow(sdbContacts);
        edtZip.setColumnName("ZIP");
        edtTown.setDataRow(sdbContacts);
        edtTown.setColumnName("TOWN");
        edtCountry.setDataRow(sdbContacts);
        edtCountry.setColumnName("CTRY_COUNTRY");
        edtBirthday.setDataRow(sdbContacts);
        edtBirthday.setColumnName("BIRTHDAY");
        edtHealthInsurance.setDataRow(sdbContacts);
        edtHealthInsurance.setColumnName("HEIN_HEALTH_INSURANCE");

        panSearch.setLayout(layoutSearch);
        panSearch.add(lblSearch, layoutSearch.getConstraints(0, 0));
        panSearch.add(edtSearch, layoutSearch.getConstraints(1, 0, -1, 0));

        panContacts.setLayout(blContacts);
        panContacts.add(panSearch, UIBorderLayout.NORTH);
        panContacts.add(navContacts, UIBorderLayout.CENTER);

        gpanDedails.setLayout(flDetails);
        gpanDedails.add(icoImage, flDetails.getConstraints(0, 0, 1, 7));
        flDetails.setHorizontalGap(15);
        gpanDedails.add(edtSalutation, flDetails.getConstraints(3, 0));
        flDetails.setHorizontalGap(5);
        gpanDedails.add(edtAcademicTitle, flDetails.getConstraints(3, 1));

        flDetails.setHorizontalGap(15);
        gpanDedails.add(lblSalutation, flDetails.getConstraints(2, 0));
        flDetails.setHorizontalGap(5);
        gpanDedails.add(lblAcademicTitle, flDetails.getConstraints(2, 1));
        gpanDedails.add(lblFirstName, flDetails.getConstraints(2, 2));
        gpanDedails.add(edtFirstName, flDetails.getConstraints(3, 2, -1, 2));
        gpanDedails.add(lblLastName, flDetails.getConstraints(2, 3));
        gpanDedails.add(edtLastName, flDetails.getConstraints(3, 3, -1, 3));
        gpanDedails.add(lblBirthday, flDetails.getConstraints(2, 4));
        gpanDedails.add(edtBirthday, flDetails.getConstraints(3, 4, 3, 4));
        gpanDedails.add(lblHealthInsurance,
                        flDetails.getConstraints(2, 5));
        gpanDedails.add(edtHealthInsurance,
                        flDetails.getConstraints(3, 5, -1, 5));
        gpanDedails.add(lblStreet, flDetails.getConstraints(2, 6));
        gpanDedails.add(edtStreet, flDetails.getConstraints(3, 6, -3, 6));
        gpanDedails.add(lblNr, flDetails.getConstraints(-2, 6));
        gpanDedails.add(editNr, flDetails.getConstraints(-1, 6));
        gpanDedails.add(lblZip, flDetails.getConstraints(2, 7));
        gpanDedails.add(edtZip, flDetails.getConstraints(3, 7));
        gpanDedails.add(lblTown, flDetails.getConstraints(4, 7));
        gpanDedails.add(edtTown, flDetails.getConstraints(5, 7, -1, 7));

        panDetails.setLayout(layoutDetails);
        panDetails.add(gpanDedails,
                       layoutDetails.getConstraints(0, 0, -1, 0));

        splitMain.setDividerPosition(250);
        splitMain.setDividerAlignment(UISplitPanel.DIVIDER_TOP_LEFT);
        splitMain.setFirstComponent(panContacts);
        splitMain.setSecondComponent(panDetails);

        setLayout(blThis);
        add(splitMain, UIBorderLayout.CENTER);
       
        setPreferredSize(1024, 768);
    }
   
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Interface implementation
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    public IContainer getContentPane()
    {
        return this;
    }

    public <OP> IContent showMessage(OP pOpener, int pIconType,
                                     int pButtonType, String pMessage,
                                     String pOkAction, String pCancelAction)
                            throws Throwable
    {
        System.out.println(pMessage);
       
        return null;
    }

    public void handleException(Throwable pThrowable)
    {
        pThrowable.printStackTrace();
    }
   
}

The application is really minimal and doesn't use a menu or toolbar, it's naked and only a frame with a panel in it. The class has 3 important parts. The first is the constructor where it gets the UILauncher:

public SimpleDBApplication(UILauncher pLauncher) throws Throwable
{
    super(pLauncher);
       
    setName("Simple DB application");
       
    initModel();
    initUI();
}

The launcher is responsible for starting an application, in our case it's the JavaFX implementation and the application will be started as real JavaFX application. The constructor sets the name (= frame title) and initializes our model and user interface.

The model is "mixed" in our simple application because we have a fat client that connects directly to the database without communication or application server. The initialization is short:

DBAccess dba = DBAccess.getDBAccess("jdbc:oracle:thin:@localhost:1521:xe");
dba.setUsername("demo");
dba.setPassword("demo");
dba.open();
               
DBStorage dbsContacts = new DBStorage();
dbsContacts.setDBAccess(dba);
dbsContacts.setWritebackTable("CONTACTS");
dbsContacts.open();
               
sdbContacts = new StorageDataBook(dbsContacts);
sdbContacts.open();

We create a new connection to our Oracle database via DBAccess and create a DBStorage for our CONTACTS table. The "real model" is our StorageDataBook because it's the class which is needed from our UI. The rest of the code:

sdbContacts.getRowDefinition().getColumnDefinition("IMAGE").
     getDataType().setCellEditor(IMAGE_VIEWER);

sdbContacts.getRowDefinition().getColumnDefinition("SOCIALSECNR").
     getDataType().setCellEditor(new UINumberCellEditor("0000"));
sdbContacts.getRowDefinition().getColumnDefinition("BIRTHDAY").
     getDataType().setCellEditor(new UIDateCellEditor("dd.MM.yyyy"));

is just column configuration and formatting, e.g. use an image viewer instead of a text editor.

The UI initialization is trivial, because it creates UI components, sets a layout and adds the components to the main panel.The interesting part in the initUI method is the model binding:

navContacts.setDataBook(sdbContacts);
...
edtSalutation.setDataRow(sdbContacts);
edtSalutation.setColumnName("SALU_SALUTATION");
edtAcademicTitle.setDataRow(sdbContacts);
edtAcademicTitle.setColumnName("ACTI_ACADEMIC_TITLE");
edtFirstName.setDataRow(sdbContacts);
edtFirstName.setColumnName("FIRSTNAME");

The first command sets the contacts databook as "datasource" for the Table view. A navigation table is a simple Table view with additional buttons for Inserting/Deleting/Editing/Exporting records.

The next command is interesting because it binds the contacts databook to an editor:

edtSalutation.setDataRow(sdbContacts);
edtSalutation.setColumnName("SALU_SALUTATION");

but with a column name which doesn't exist in our database table: SALU_SALUTATION.

This is a special feature of DBStorage because it checks foreign key columns and creates dynamic "not existing" columns for referenced tables. It's a little bit magic but it reduces source code to a bare minimum. The contacts storage also knows how to fetch such "not existing" columns because we don't have storages or databooks for our SALUTATIONS or ACADEMICTITLES table. We don't need references because our contacts storage handles everything. Oh.. this magic can be deactivated and it's always possible to do the same programatically, but we love the so called "automatic link detection".

The rest of the class is not relevant, e.g.

public IContainer getContentPane()
{
    return this;
}

public <OP> IContent showMessage(OP pOpener, int pIconType,
                                 int pButtonType, String pMessage,
                                 String pOkAction, String pCancelAction)
                        throws Throwable
{
    System.out.println(pMessage);
       
    return null;
}

public void handleException(Throwable pThrowable)
{
    pThrowable.printStackTrace();
}

It's only the implementation of some abstract and interface methods, but it has no functionality.

And that was all you need to create a simple CRUD application. It's fully functional and allows Insert/Update/Edit operations without additional code. It makes no difference if you edit directly in the table or in the form via editors. The editors work automatically with the right datatype and show a combobox in case of FK columns and a date chooser in case of a date datatype, e.g.:

Combo Box

Combo Box

There's no need to create a java.sql.Connection manually or use java.sql.PreparedStatement for insert/update/delete. This funcationality was encapsulated in JVx' DBStorage and we simply use the functionality for our JavaFX UI and in all other UI technologies as well.

JVx and JavaFX (fasten your seat belts)

Post to Twitter

Today is a great day, because I'm able to write about our latest Research Project.
It has to do with JavaFX and JVx - what else.

If you remember one of my blog postings in 2013, you know that we tried to implement our JVx UI with JavaFX. Long story, short: Not possible with one master thesis.

But... we always try to reach our goals and it's an honor to tell you that we're back on the track.

Our JavaFX UI is one of the coolest things on this IT planet.

It's cool because of JavaFX. It's APIs are still not perfect and the overall performance could be better but it has so many awesome things. Two of them are styling via CSS and animations. JavaFX is the official replacement for Swing and it has potential to bring back "write once run anywhere". We don't know if JavaFX will be the holy grail for desktop application development but there's nothing comparable in the Java world.
To be honest, it's not a problem for us because our JVx is (UI) technology independent and if JavaFX won't do it, we'll implement the next UI toolkit for JVx.

... but back to the topic

You should already know that JVx is a Full-stack application framework for building database (ERP) software. It's GUI technology independent and we've different UI implementations. The most important ones are based on Swing and vaadin. We also have native Android and iOS support and our applications run on embedded devices as well. Our applications don't need an application server to work properly because we have a plain socket server as well (IoT ready).

Our preferred Java UI technology was and is still Swing, because it's feature rich and you can build every kind of application without bigger problems. The problem with swing is that it's old and not maintained from Oracle. Sure it will work with future Java releases without bigger problems (hopefully) but you can't use modern development techniques or create fancy and fresh UIs with less effort. A standard Swing application looks boring and cold. Sure, it's possible to pimp a swing application with lots of pictures or nice looking LaFs, but it's not fun.

Swing development is like software development in the stone-age, compared to state-of-the-art web development. If you do web development, you have CSS3 and HTML5 and some nice designed UI frameworks. I won't say that you're faster or that web development is better but it's easier to build nice looking UIs. I'm not a big fan of classical web development because most applications are horrible to maintain, there are soo many different frameworks and you can't be sure that your investment is future-proof because many frameworks don't survive the first year. But everything depends on knowledge and the right framework - for sure.

IMO it's better to use one framework that solves most of application relevant problems - if it exists. If we talk about web applications, I recommend vaadin because it's feature rich, just works, is very polished and is licensed under Apache 2.0. The community is active and great support is available!
Sure, there are some other frameworks like GXT or extJS from Sencha, but the license could be a problem if you build commercial applications. Don't forget RAP, but it's not trivial to start with the whole Eclipse ecosystem.

I'm talking about (enterprise) application development and not about webpage or webservice development. I would recommend pure HTML5 and CSS3 with one or two smaller javascript libs for developing fancy "webpages", or php instead of Java. If Java and "webpages", PrimeFaces is a good decision.

Soo, if Swing is "dead" and web development is hard, what should we use for (enterprise) application development?

If you develop for the desktop, try to start with JavaFX becasue it's maintained and has potential for mobile devices.
If you develop web applications, use vaadin.

(This is my personal opinion, mixed with my technology and project experience)

And why should you use JVx?

JVx bundles all together in one independent framework (it's still small and easy to learn). You'll be independent of the underlying UI technology. Write your application once and start it with different UI technologies or use it on different devices without problems. Your investment is future-proof. JVx is open source and licensed under Apache 2.0. JVx doesn't hide the underlying technology. It's always possible to use features from the technology if you want. If you use a UI without abstraction, you can't switch to another UI easily without rewriting most parts of the UI. If you start with e.g. JavaFX and if Oracle will abandon JavaFX, will you rewrite the application?

You can compare the concept of JVx with SLF4J, but with a different use-case: (enterprise) application development

And what about JavaFX?

I like JavaFX because of, IMO, 5 great things: scene graph, CSS styling, Animations, Web engine and the community. All other things are important and useful but not essential for real-world applications (vs. Swing). It's not possible to compare Swing with JavaFX because JavaFX was written from scratch and Swing was based on AWT. Sure, both toolkits are UI toolkits :) but with different concepts.

JavaFX bridges the gap between desktop and web development. It's still a toolkit for desktop applications but with most advantages of web development. It's possible to create fancy applications without headache. There are cool tools like Scene Builder or ScenicView. IDE support is getting better and developing controls is not too hard.

Sounds too good? JavaFX is on the right track and ready for real-world applications.

That was enough for us to start a JavaFX UI for JVx. It's the only framework which allows switching between Swing, Web, JavaFX or native mobile. Sounds unbelievable? Here's is our first video with our JavaFX implementation. It demonstrates the full power of JVx and JavaFX.

The video starts with our good old Swing UI. The same application will be shown as web application in a Chrome browser. And finally it will run as JavaFX application. You should carefully check the JavaFX application because it has an embedded browser that shows the same application, with a different UI technology - at the same time. Don't miss the animations :)

It's a world premiere!

JVx and JavaFX



We know that the styling is not really cool but we're currently in the development phase and aren't finished... be tolerant :)

Our Live-Ticker implementation - World cup 2014

Post to Twitter

We got a lot of (positive) feedback for our live ticker in Packung! It's great to see actual results depending on the current score :)

We started our live ticker in 2008 with a simple implementation in Java. The solution simply sniffed results from standard html pages. It was easy to get results because result pages didn't use Ajax. It was harder in 2010 and 2012 because Ajax was heavily used. But we found some pages without Ajax.

What's the problem with Ajax? It's not possible to get the complete html content with one simple http request with Java. You need a "browser" that handles javascript and async calls.

We didn't find a html page with live results this year, which didn't use Ajax or Flash. So we thought about a clever solution because we won't pay for "free content". And of course, our betting game is free and we don't earn money. But what's a clever solution? The task is simple: Grab results from existing live-ticker website (be aware of Ajax) - but how?

The solution is simple: Use a browser :)

But Java doesn't offer a browser control, doesn't it? Yes, JavaFX does!

In 2008 we played around with QT webkit control of QT Jambi (but the project is not active) because it was a nice browser control. The problem was that it didn't work headless and our application server was a linux box without X-Server. This year, we had the same restrictions because our server is a linux box and still no X-Server. We thought that JavaFX could be the right solution for us...

BUT it doesn't work headless enough - not with latest release. It will be better in upcoming releases and/or custom JavaFX builds because there is a Glass windowing component called Monocle. But this was the right challenge for us.

Our current solution runs on Linux, without a real X-Server. It's a JavaFX WebView/WebEngine that reads data from a public live-ticker.

How our solution works?

Install important linux packages:

yum install xorg-x11-server-Xvfb xorg-x11-xauth gcc glib2 glib2-devel libtiff libtiff-devel libjpeg-devel cairo cairo-devel pango pango-devel redhat-lsb

optional if available

yum install redhat-lsb-graphics

Download and install ATK

wget ftp://ftp.muug.mb.ca/mirror/centos/6/os/x86_64/Packages/atk-1.30.0-1.el6.x86_64.rpm
wget ftp://ftp.muug.mb.ca/mirror/centos/6/os/x86_64/Packages/atk-devel-1.30.0-1.el6.x86_64.rpm
rpm -Uvh *.rpm

and GTK+

wget http://ftp.gnome.org/pub/gnome/sources/gtk+/2.18/gtk+-2.18.2.tar.gz
tar -xvf gtk+-2.18.2.tar.gz
cd gtk+-2.18.2
./configure
make
make install

Start Xvfb:

Xvfb :99 &

Start application:

#!/bin/sh
export DISPLAY=:99
export LD_LIBRARY_PATH=/usr/local/lib
./jdk1.8.0_05/bin/java -jar livescore.jar >> liveupdate.log 2>> liveupdate_err.log &

The application itself is trivial. It's a class with main method:

/**
 * Starts the application.
 *
 * @param pArgs the application parameters
 */

public static void main(String[] pArgs)
{
    new Livescore();
}

/**
 * Creates a new instance of <code>Livescore</code> and starts the update procedure.
 */

public Livescore()
{
    super();

    fxPanel = new JFXPanel();
       
    Platform.runLater(new Runnable()
    {
        public void run()
        {
            fxPanel.setScene(new Scene(new BorderPane(), 800, 600));
               
            browser = new SimpleBrowser();
            browser.load();
        }
    });
       
    start();
}

We need the JFXPanel because JavaFX needs a scene, otherwise it'll throw Exceptions. We don't need the instance for other things.

The SimpleBrowser class is similar to this snippet:

/**
 * Loads the configured website.
 */

public void load()
{
    if (view == null)
    {
        view = new WebView();
        engine = view.getEngine();

        engine.load("http://live-ticker.url");
    }
    else
    {
        engine.reload();
    }

    if (thCheck == null)
    {
        thCheck = new Thread(new Runnable()
        {
            public void run()
            {
                try
                {
                    while (Thread.currentThread() == thCheck)
                    {
                        Platform.runLater(new Runnable()
                        {
                            public void run()
                            {
                                checkResults();
                            }
                        });
                       
                        Thread.sleep(10000);
                    }
                }
                catch (InterruptedException ie)
                {
                    //stop
                }
               
                thCheck = null;
            }
        });
        thCheck.start();
    }
}

That's it. We got two "silent" Exceptions

java.lang.NullPointerException
  at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:57)
  at sun.reflect.UnsafeObjectFieldAccessorImpl.get(UnsafeObjectFieldAccessorImpl.java:36)
  at java.lang.reflect.Field.get(Field.java:387)
  at com.sun.javafx.webkit.theme.ScrollBarThemeImpl.getFieldValue(ScrollBarThemeImpl.java:397)
 ...
Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
  at com.sun.javafx.webkit.theme.ScrollBarThemeImpl.thumbPosition(ScrollBarThemeImpl.java:267)
  at com.sun.javafx.webkit.theme.ScrollBarThemeImpl.getThumbPosition(ScrollBarThemeImpl.java:352)
  at com.sun.webkit.Timer.twkFireTimerEvent(Native Method)
  at com.sun.webkit.Timer.fireTimerEvent(Timer.java:66)
  at com.sun.webkit.Timer.notifyTick(Timer.java:47)
  at javafx.scene.web.WebEngine$PulseTimer$2.pulse(WebEngine.java:1154)
  ...

after the application start, because of some scrolling problems, but these Exceptions had no effect (in our solution).

The solution would rock with a real headless JavaFX but it's ok for us.

JVx with JavaFX and Vaadin and Exchange appointments

Post to Twitter

Wow, what a title :)

Some weeks ago, I blogged about JVx with Exchange servers. The project now supports Appointments and Tasks.

To demonstrate some features, we created an application that integrates powerful frameworks. We took Vaadin with Calendar AddOn, JavaFX' webview and integrated all together in a standard JVx application.

Take a look:

JVx + JavaFX + Vaadin + Exchange

JVx + JavaFX + Vaadin + Exchange



Did you notice that the calendar is a JavaFX WebView?

Our exchange storages still use the EWS Java API but now with some tweaks.
More information will follow in other posts...

Control your window shutters with JavaFX

Post to Twitter

If you have remote controlled, electronical, window shutters, continue reading. If you don't have them, this article might not be interesting for you?
I have such window shutters and a lot of windows. I don't own a control center that allows me to automatically open and close my shutters every morning and evening. I do it every day manually. Sometimes sooner and sometimes later - depends on the day of the week.

Why I don't own a control center?
Such control centers are expensive and do not offer expected features. Some of my features are controlling via SMS, controlling via Internet, move shutters to a specific position (not only up and down).

I didn't really search for a solution of my "problem" because it was not a problem, it was an everyday task and it was habitual. Since I own a RaspberryPi, I tried to find useful solutions, not only Hello worlds. That's the reason why I tried to control my window shutters via Raspberry Pi.

The first big problem was, that it was not possible to find a sender for my shutters. The frequency was 433,42 MHz, but there were no ready-to-use solutions. It was not my intention to solder my own sender and record and decrypt the signal. So, I decided for the easy way: Connect an existing remote control with my Pi. It makes no difference for the remote control, if you press buttons manually or a Pi does the job.

It was not a big problem to find some cheap 3V relais. The soldering was fun and not rocket science. Here is the result:

Remote control

Remote control

My remote control got a new brain. It works with and without connected Pi. I didn't damage it :)
The only thing I had to do, was programming every receiver with one (or more) channel of my remote control. Because one button should control all window shutters. But that was a task of 5 minutes.

The hardware was ready and the next step was a fancy client application for controlling my Pi. The plan was a simple application that shows one of my windows. The shutter should move like the original. The application should connect to my Pi via my LAN.
I used JVx and vert.x on server-side for the GPIO control, wrote a nice Deskotp application with JavaFX, some transitions and JVx for the communication to my server. And here is the result:


JavaFX application

The application moves the shutter like its big brother. One feature is transparency on mouse over, because you wanna know the position if you stop it.

And if you are interested how software and hardware acts together:


JavaFX Shutter control

The project was really cool because the source code for the whole thing was minimal, the costs were not comparable to a control center from the manufacturer and I have all features I ever wanted.

Interested in the source code?

Server-side, JVx style:

//----------------------------------------------------
// Application.java
//----------------------------------------------------

public GpioController getController()
{
    GpioController gpio = (GpioController)get("controller");
       
    if (gpio == null)
    {
        gpio = GpioFactory.getInstance();
               
        put("controller", gpio);
    }
       
    return gpio;
}

public GpioPinDigitalOutput getDownPin()
{
    GpioPinDigitalOutput pin = (GpioPinDigitalOutput)get("downPin");
       
    if (pin == null)
    {
        pin = getController().provisionDigitalOutputPin(RaspiPin.GPIO_01, "Down",
                                                        PinState.LOW);
               
        put("downPin", pin);
    }
       
    return pin;
}

//---------------------------------------------
// Session.java
//---------------------------------------------

public void moveDown() throws Exception
{
    GpioPinDigitalOutput pin = getDownPin();
       
    pin.high();
    Thread.sleep(2000);
    pin.low();
}

And my client simply calls:

NetSocketConnection con = new NetSocketConnection("10.0.0.39");
                       
macon = new MasterConnection(con);
macon.setApplicationName("shutter");
macon.setUserName("user");
macon.setPassword("pwd");
macon.setAliveInterval(-1);
macon.open();

macon.callAction("moveDown");