JavaFX FlowPane vs. FXFluidFlowPane
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
- 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.
- The FlowPane doesn't support alignment of managed nodes
but should:
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:
JavaFX, JVx, CalendarFX and Exchange Server
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:
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
I'm happy to show you a first screenshot of our new Stage style and our MDI system:
Compared to the standard stage:
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.
JVx 2.2
Today is a great day because I'm very happy to announce JVx - version 2.2.
It's an awesome release with soo many new features.First of all, the LoC analysis. Here are some very interesting numbers for you:
JVx library Swing UI LoC Type 73.759 Code 62.025 Comments (~ 46% of Code) 19.662 Empty lines 155.446 Total LoC Type 34.032 Code 18.882 Comments (~ 36% of Code) 7.662 Empty lines 60.576 Total JVx library (Test cases) LoC Type 21.093 Code 11.441 Comments (~ 35% of Code) 7.300 Empty lines 39.834 Total Not bad if you compare it with other releases: 0.8, 1.0, 2.0.
We've still a very small codebase, compared to the features. The code quality and test coverage are still "green".Some additional numbers:
Files and Tests JVx library source files 594 Swing UI source files 135 Test source files 140 Total 869 Still a small codebase and still very easy to maintain. Here are more numbers, about testing:
Unit tests (no UI, without manual perf. tests) 958 Class coverage (without UI) 81% Method coverage (without UI) 69% Our coverage got better since 2.0 - well done. TOP 10 classes
Classname LoC MemDataBook 4.028 DBAccess 3.772 JVxTable 2.510 ArrayUtil 2.224 StringUtil 1.738 Server 1.715 DBStorage 1.542 JVxFormLayout 1.243 FileUtil 1.182 SwingApplet 1.102 Sure, MemDataBook and DBAccess are our main classes but we should check if it'll be possible to reduce complexity!
And last but not least, a short overview of new features:
- Performance tuning
JVx got a boost. We pimped our model to be super fast. Sure, it was fast and we didn't have any problems... but during some code reviews, we found some lacks because of gc calls. The memory consumption wasn't perfect and not gc friendly. We created a lot of temporary String[] and event objects, even if the weren't needed.Long story, short: Inserting 2.000.000 records with 16 columns was done in about 23 seconds. Now: 1.5 seconds.
- Support for IoT/M2M/Microservices
We pimped our remote communication a little bit. It's now possible to embedd JVx, especially the server class, in any environment that supports Java. We had an implementation for Java application serves like Tomcat or Wildfly, but no solution for plain socket servers. We put a lot of work in this part of JVx. We now have out-of-the-box solutions for vert.x or plain socket servers. It've never been easier to write remote applications with JVx.
- Better JNDI support
We had JNDI support since JVx 1, but now it's bulletproof. Use JNDI to configure your database connection or the whole application. It's possible to load application config.xml via JNDI. Same is valid for server configuration. We allow, so called, virtual configurations.
- @PostConstruct and @PreDestroy
We've support for both annotations in our server-side Lifecycle objects. It's now possible to remove your workarounds like:
if (getClass() == Application.class)to find out whether the application LCO or session LCO was created. The session LCO extends the application LCO and otherwise it wasn't possible to do things only if application LCO was created, because constructor was called for every session LCO again.
- Lambda support
It's soo much fun, working with lambdas and Java 8. We're happy to have full support for lambda expressions in JVx especially for event listener handling. And it's backwards compatible (Java 6, 7) without restrictions.
Be sure that you recompile your JVx projects with JVx 2.2 library. - Log4j support
We love using built-in Java APIs like Logging API - because it doesn't need extra libraries. But we're open for 3rd party libraries and log4j(2) is too popular to ignore it. So we have official support for it in JVx. Simply use the new log factory com.sibvisions.util.log.log4j.Log4jLoggerFactory for your application and configure your loggers via log4j.properties or log4j2.xml.
- Spring security
We had a customer request for supporting SAML 2.0 authentication. SAML what?
Yes, we had the same question, but found that Spring framework had a solution for it: Spring Security SAML. We didn't reinvent the wheel and connected Spring security with our security mechanism. Here's the source code. - HanaDBAccess
HANA is the in-memory db solution of SAP. We've a connector for it. But be careful, because we weren't allowed to tune the database
- Bugfixes
The list isn't complete but contains some important features. The full changelog is available here.
Drag'n'Drop support for JavaFX' TabPane
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)
News mix
The next release of JVx is coming!
It'll be version 2.2. We have 2 open tickets for this release but both are refactoring tasks, nothing too important. We're already feature complete.
It'll be an awesome release because we have a lot of performance improvements and some really cool features like lazy loading of BLOBs (binary content). The server implementation is now IoT ready and it's pluggable. Support for Lambda expressions with full backwards compatibility for our listener handling. And we have support for application monitoring, style definitions, streamed upload of large files, and much more.
We plan the release for the end of this week.
Our VaadinUI was based on vaadin 7.1.15 and 7.4 was released some days ago. We're happy to announce support for vaadin 7.4.
The repository of JVx' vaadin UI already contains 7.4 libraries and everything is available for you. We don't have an official release right now, because we do some tests and afterwards we'll release a new version of our UI. We don't have a date for you but hopefully in March.
We currently don't use the new Grid component, but we'll replace the current table implementation asap.
The last information is about the next release of VisionX 2.2 preview. It will contain JV' vaadin UI for vaadin 7.4 and some new features like exporting an application for embedded devices (run the application on embedded devices like Raspberry Pi, without application server - "Micro applications").
Oh, finally... Check our github page, because we have new projects for you.
A DesktopPane for JavaFX
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:
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
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:
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
beginIF :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:
The birthday field is a 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.:
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.
Better Lambda support for JVx
I wrote about JVx and Java 8, Events and Lambdas some weeks ago.
It was a first test with JVx events and Lambdas and it was great to see that everything worked as expected, BUT not all JVx events were supported because not all listeners were defined as SAM type.
It was great to see that Lambdas were supported with our event handling implementation but it was a shame that only action events and some model events were really supported. So we started thinking
Our key listener was defined like this:
public interface IKeyListener
{
public void keyTyped(UIKeyEvent pKeyEvent);public void keyPressed(UIKeyEvent pKeyEvent);
public void keyReleased(UIKeyEvent pKeyEvent);
}The problem was that there were 2 methods too much. So we split the interface in 3 new interfaces:
- IKeyPressedListener
- IKeyReleasedListener
- IKeyTypedListener
Every listener has exactly one method, e.g.
public interface IKeyPressedListener
{
public void keyPressed(UIKeyEvent pKeyEvent);
}The new listener, for backwards compatibility, is this:
public interface IKeyListener extends IKeyTypedListener,
IKeyPressedListener,
IKeyReleasedListener
{
}Our listener are handled from event handlers, like KeyHandler. The old implementation was:
public class KeyHandler extends RuntimeEventHandler<IKeyListener>
{
public KeyHandler(String pListenerMethodName)
{
super(IKeyListener.class, pListenerMethodName);
}
}(The handler creation with a method name wasn't really typesafe but it worked)
We refactored the handler a little bit and the result is:
public class KeyHandler<L> extends RuntimeEventHandler<L>
{
public KeyHandler(Class<L> pClass)
{
super(pClass);
}
}After some refactoring, our listeners were Lambda and backwards compatible, e.g.:
userName.eventKeyPressed().addListener(e -> System.out.println(e.getKeyChar()));The next nightly build will contain all changes!
There are still some smaller todos, but Lambdas are well supported.VisionX 2.2 Preview Release
The first preview release of VisionX 2.2 is available. The exact version number is 2.2.41.
It available as trial version or for our customers via download area.
The preview release contains updates of all dependent frameworks like JVx and vaadin UI.
Here's a list with more details:
- Responsive web applications
Window shutter web control
- Browser navigation
- Application styles
- Support for embedded devices
- Style definition with VisionX
- New EPlug support
- Custom Database connection strings (e.g. Oracle TNS entry)
- Live Template support for work-screens
- XLSX reports instead of XLS
- Ready for application monitoring
- UI improvements
- Ready for native Android client
Sure, we fixed some bugs and we also improved overall performance because of improvements in JVx. The most interesting thing is the support for embedded devices like RaspberryPi.
Have fun with VisionX
- Performance tuning