Customizing the e4 main window with e(fx)clipse

In e4 the main user interface is constructed (rendered) based on the e4 application model. If you want to change the way that a specific model element is represented in the ui, you can plug in a custom renderer for this type. This approach is very powerful, but can be a technical challenge to get working the way you want it to. If you only want to make minor adjustments to the main window, like adding a search field to the menu bar, there is now an easier way with the e(fx)clipse 2.2.0 default window renderer using FXML.

Starting Point

The starting point for this introduction will be a small demo app I developed to show case this feature. The application model only has one part but uses every possible window trim location to place some buttons.

2015-11-27_133710

Yes, it’s ugly … but don’t worry … that will get even worse! 😉

2015-11-27_120129

The full demo application is available as part of the demos in the e(fx)clipse Git repository. To run it you need to check out all the org.eclipse.fx.demo.customWindow.* projects.

Customize the locations of window ui elements

Setup of the FXML

If we want be able to control the layout of the window, we need to tell the renderer where to put the children of the window. This is done using specific IDs that the renderer will look for after loading (checking the property id, not fx:id).

ID UI Elements Default Location
client-area children (sashes, stacks, part, …) center of the main BorderPane
menu-bar-area main menu bar top of the main BorderPane
top-trim-area top window trim bar (toolbar, tool control) top of the client area
bottom-trim-area bottom window trim bar (toolbar, tool control) bottom of the client area
left-trim-area left window trim bar (toolbar, tool control) left of the client area
right-trim-area right window trim bar (toolbar, tool control) right of the client area

The root element of the FXML has to be a JavaFX BorderPane or a org.eclipse.fx.ui.controls.stage.TrimmedWindow. In most cases using the BorderPane will be what you want to do. You only need to use a TrimmedWindow if you also need control over the window decorations (like the min/max/close buttons on the top).

Hook the FXML to the window

It is very easy to hook an FXML to a window in the application model. All you need to to is set the property efx.window.root.fxml in the persisted state of the window. The value is an URL pointing to the FXML you want to use.

2015-11-27_121821

Examples

Insert a search field into the menu bar

This FXML will insert a TextField in the top row next to the main menu bar. The menu bar will be placed inside the HBox with id="menu-bar-area". The rest of the windows elements (trim bars, client area) will be placed by default. In this example I left out the properties maxHeight and maxWidth which I usually set to Double.MAX_VALUE for all containers, so you can focus on the structure of the window.

<BorderPane fx:controller="org.eclipse.fx.demo.customWindow.app.controller.MainFrameController">
	<top>
		<VBox>
			<children>
				<HBox>
					<children>
						<HBox id="menu-bar-area" />
						<TextField fx:id="searchField" promptText="Search..." />
					</children>
				</HBox>
			</children>
		</VBox>
	</top>
</BorderPane>

As you can see in this screen shot, we now have a text input field next to the menu bar:

2015-11-27_120350

To use the new search field in my application I can inject it into the controller I defined with fx:controller in the FXML. Here I use the e(fx)clipse EventBus to forward a search request, but there are lots of ways on how to proceed from here. Notice that it is possible to use dependency injection to get hold of all the e4 and OSGi services in the controller!

public class MainFrameController implements Initializable {

    @Inject private EventBus eventBus;

    @FXML TextField searchField;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        searchField.textProperty().addListener((observable, oldValue, newValue) -> {
            eventBus.publish(new Topic<String>("app/event/search"), newValue, true);
        });
    }
}

Go crazy

In the full demo I placed every trim bar on the outside of the window so I can insert a “cool” separation bar between the client area and the trim bars (yes … the blue bars with the light blue circles … I warned you it would get ugly!). I also thought it would be totally awesome to place the tool bar on top of the menu bar just to … well … show thats it’s possible, I guess. 😉

2015-11-26_132047

Please have a look at the complete FXML. I also put the “client area” in there, although I could have skipped this, since it would be placed in the center of the main BorderPane by default anyway.

Using SceneBuilder

You might notice that I sometimes used minWidth and minHeight at some places in the FXML. I also gave the root BorderPane a prefWidth and prefHeight. I did this, so that the pane visually resembles the final outcome if you open it with SceneBuilder. This is not really necessary for the correct rendering and can look broken, if you plan to remove trims at runtime, since the container will then leave an empty space where the trim can be. If you omit the minimums the container will shrink to 0 and will therefore become invisible when the trim is removed.

2015-11-27_123325