↓ Skip to main content
  1. JavaFX/
  2. Example Projects/

Example: Hexadecimal Editor

7 mins·
Table of Contents

Introduction
#

This was originally posed as a question on Reddit about writing vast amounts of Text onto a Canvas, because “TableView cannot handle the huge amounts of data to be displayed”. Of course, trying to have giant Canvas in a ScrollPane with thousands of lines of data is absolutely not the way to go. On the other hand TableView is exactly designed to handle large data sets while minimizing the use of screen resources.

Once this issue was sorted out, the OP continued on with TableView, but it seemed to me that this was better done as a ListView, and I wondered what it would take to do it. So, here it is.

ScreenShot

Source Code
#

The source code for this project can be found here

Features
#

There’s a set of CheckBoxes at the bottom of the screen that control the display of the various formats of the data; octal, binary, decimal and character. Each ListCell has an “Edit” Button which controls the display of the bottom row of the ListCell which has a TextField and a “Commit” Button.

The display is broken up into 16 byte ListCells and each byte of the 16 is displayed in a VBox in the various formats.

All of the actual editing is done through the TextField in the bottom of the ListCell. When editing is happening and the TextField has focus, the hexadecimal display of byte closest to that of the caret in the TextField is highlighted. This isn’t perfect, but early testing revealed that without this indicator it was a little difficult to determine where the caret location corresponded to the byte by byte display.

Programming Notes
#

Here’s an overview of how the application is designed, with a few particulars highlighted…

Framework
#

Even though this application simply generates a few thousand bytes of random data, it has been designed with an Model-View-Controller-Interactor framework. There’s no file handling in this example, so there’s no Service level, and the Interactor generates the dummy data.

Even given the simplicity of this, you can still see how the MVCI framework works.

The Data Model
#

The data model for the application is composed of a single ObservableList<HexRow>. HexRow itself is composed of an Integer to hold the file offset of the first byte in the row, and a ByteArray to hold the actual data.

Note that neither of these to elements is an Observable type of value. There is some overhead to Observable data types, as they have to track Listeners and bound values and invalidation and so on. Generally this isn’t an issue, but if there are millions of bytes of data, each wrapped in an Observable class, it might get out of hand.

What do you lose when the HexRow elements are not Observable? Not a lot for this kind of application. The ListCells are all autonomous, and the Observable elements are all in the local data model inside the ListCell. This is updated via updateItem when the ListCell is recycled, and the code in the “Commit” Button updates item directly.

What is lost is the ability to have the ListCells all automatically redisplay if bytes are added or removed from any row. However, handling this case is going to take a fairly heavy amount of processing regardless, as each HexRow will need to be updated, taking data from one neighbour or another in turn down to the end of the file. In which case the whole list can probably be invalidated, which should cause the ListCells to reload.

ByteArray is interesting itself. It seems to be a Kotlin only data type (yeah Kotlin!) and has some nifty methods to convert to a String in hexadecimal format. It made most of the coding pretty easy.

Data Conversions
#

All of the data conversions are done off the hexadecimal String representation of the ByteArray held in the StringProperty encapsulated in ObservableValue.map() functions. This means that these values stay in lock-step as the StringProperty is edited, or the value in the StringProperty is updated via updateItem().

The primary method is called extractHex which pulls two digits from the String based on an index. If the String isn’t long enough, it returns null, so this method returns a String?. The subsequent conversions need to deal with this nullability. Kotlin has a nifty function, String.hexToInt() that takes the String representation of a hexadecimal number and converts it into an Integer, which can then by converted in a different base to display octal or binary through Int.toString(radix).

private fun extractHex(bigString: String, index: Int): String? =
  if (bigString.length > ((index * 2) + 1)) bigString.substring(index * 2, (index * 2) + 2) else null

@OptIn(ExperimentalStdlibApi::class)
private fun toRadix(hexString: String, radix: Int) = hexString.hexToInt().toString(radix)

private fun extractRadix(bigString: String, index: Int, radix: Int) =
   extractHex(bigString, index)?.let { toRadix(it, radix) } ?: ""

@OptIn(ExperimentalStdlibApi::class)
private fun extractChar(bigString: String, index: Int) =
   extractHex(bigString, index)?.hexToInt()?.toChar()?.toString() ?: ""

You can see that the String.hexToInt() function is still experimental, but it seems to work well.

Controlling the Rows
#

There’s a data structure called HexCellControlModel. This is composed of a set of BooleanProperties. The main View instantiates this Model and then passes it to the HexEditorCells in their constructors. This way, each HexEditorCell will have a reference to the single Model instance. Then the CheckBoxes at the bottom of the GUI have their Selected Property bound to one of these BooleanProperties in HexCellControlModel.

Inside the HexEditorCell the Labels that display the values have their Visible and Managed Properties bound to one of the BooleanProperties in HexCellControlModel.

In this way, all the ListCells respond in unison to each of the CheckBoxes.

The Heading Row
#

The Labels across the top of the ListView are actually not part of the ListView itself. The entire View is a BorderPane with the centre occupied with just the ListView. The bottom has an HBox with the CheckBoxes in it, and the top has an HBox with the heading Labels in it.

All of the VBoxes that make up the main part of the HexEditorCell have a fixed minimum width, and the Labels in the heading row follow along with that.

There’s one quirk, the binary display takes significantly more horizontal space than the other modes, so when it is visible the minimum width has to increase to hold this. This is achieved by using ObservableValue.map() to convert the BooleanProperty into a ObservableDoubleValue that could then be bound to the MinWidth Property of the Labels.

Editing
#

Originally, I pondered the idea of having context menus on the Labels for the hex values, and that’s still something that could be added. Instead, I reasoned that the byte by byte display was good for seeing and understanding the data, but it was probably just easiest to edit it as a String in a TextField.

To make it a bit easier to follow how the editing in the TextField related to the byte by byte display, a PseudoClass was added to the hex Labels to indicate that they corresponded to the values near the caret in the TextField. This PseudoClass is styled to put a border around the Label and change the background to a light blue. The border needed to be given insets of “-2” on the top and bottom to avoid having the Label grow vertically when the borders were added and displacing the contents of that column downwards.

The TextField as supplied in this example doesn’t have a Filter or TextFormatter to prevent non-hex digits from being added. This is something that would be needed to put this into production.

Conclusion
#

Virtually all of the cool stuff is inside the HexEditorCell, as expected. My original advice to the OP was:

Think if you just had 16 bytes of data to edit on a screen. Not millions of bytes, just 16 and a whole screen to put a layout on.

Would you carve it up into 16 independent cells? Probably not.

What I would probably do is create an HBox with 16 VBoxes inside. Each VBox would hold a Label, one each for hex, octal, binary, decimal and character.

And that’s exacly the process that I took here. Think of how you’d want to see or edit just a handful of data, and design a layout to serve that purpose. Then put it into a ListCell and let the ListView virtualization do the work of loading the data into it.

Related

Using ListView Cell Layouts

15 mins
Introduction # This is the second article in a series that started with Understanding ListView. If you’re new to ListView you might want to take a look at that article first, as it explains some important concepts about how ListView works, and we’ll be building on that information in this article.

Understanding ListView

10 mins
Introduction # In JavaFX, ListViews are everywhere. They’re used as the pop-up in ComboBoxes and some other controls, so many programmers are familiar with them even if they haven’t directly used ListView. Most programmers, however, never go very deep into understanding ListView, they’ll simply create an ObservableList of String and then stuff it into a vanilla ListView to put a scrolling list on the screen. To do anything more interesting, they’ll jump over to TableView instead.

Handling TableView Cell Data

25 mins
Introduction # Once you’ve mastered the basic components of TableView layout and you try do something in the real world, you’ll inevitably run up against data that doesn’t seem to work nicely with TableView. We’re going to look at how to wrestle with real world data and how you can work effectively with it in your TableViews. The approach that we’re going to take is to try to understand how data moves into your TableView, and that really boils down to understanding how it gets into your TableCells.

TableView Basics

19 mins
Introduction # For some reason, tables (so in JavaFX, TableView) are one of the first elements that programmers want to include in their screens. Yet at the same time, they are one of the more complicated screen elements and the concepts behind them are a bit more advanced than most beginners are ready to handle.

How To Build a Hex Map Based Application

16 mins
Introduction # Hex Maps, or hex boards are a popular platform for tabletop games, especially wargames and Role Playing Games. They are uniquely useful because hexagons fit nicely into a grid and there are 6 directions from each hexagon (or just, “hex”), compared to just 4 with a grid made up of squares.

Dealing With Dialogs

20 mins
Introduction # We’re all familiar with Dialog boxes, they pop up with some important information, ask you, “Are you sure?”, let us pick a file, or make some other important decision before processing can continue. In the beginning, JavaFX didn’t have Dialogs and we were left to cook up our own solutions or use third party libraries. But they were added around JavaFX 8, and work quite well.