Skip to content
Qualtagh edited this page Jul 11, 2020 · 5 revisions

This tutorial explains how to merge cells in a table itself (not in a header).

We'll take quick start source as the starting point and change its data:

data.setValue( 0, "FIRST_NAME", "John" );
data.setValue( 0, "LAST_NAME", "Doe" );
data.setValue( 1, "FIRST_NAME", "Jane" );
data.setValue( 1, "LAST_NAME", "Doe" );
data.setValue( 2, "FIRST_NAME", "Anony" );
data.setValue( 2, "LAST_NAME", "Mouse" );
data.setValue( 3, "FIRST_NAME", "William" );
data.setValue( 3, "LAST_NAME", "Perry" );
data.setValue( 4, "FIRST_NAME", "Morgan" );
data.setValue( 4, "LAST_NAME", "McQueen" );
data.setValue( 5, "FIRST_NAME", "Katie" );
data.setValue( 5, "LAST_NAME", "McQueen" );
data.setValue( 6, "FIRST_NAME", "Albert" );
data.setValue( 6, "LAST_NAME", "Newmann" );
data.setValue( 7, "FIRST_NAME", "John" );
data.setValue( 7, "LAST_NAME", "Goode" );
data.setValue( 8, "FIRST_NAME", "Vanessa" );
data.setValue( 8, "LAST_NAME", "Key" );
data.setValue( 9, "FIRST_NAME", "Robert" );
data.setValue( 9, "LAST_NAME", "Peterson" );

We would also need a row sorter (it's a standard Swing row sorter):

table.setAutoCreateRowSorter( true );

A picture we get:

New model result

JRubik library (it's listed in the Alternatives section) lets to manually merge cells at the view level. Another approach was chosen for JBroTable: cells merging rules are set at the model level. And view determines at rendering stage if cells need to be merged according to these rules. These rules may seem weird and hard to customize at first glance but it's much easier to solve tasks like row sorting or columns rearrangement using this approach.

Suppose we want to merge cells in the LAST_NAME column for equal last names.

table.setUI( new JBroTableUI()
  .withSpan( new ModelSpan( "LAST_NAME", "LAST_NAME" ).withColumns( "LAST_NAME" ) ) );

Using this code we get the following picture:

Last name merged

Cells with values McQueen and Doe are merged. Try to sort by FIRST_NAME column. The sorting breaks the order of McQueen family. Robert Peterson stands now between Morgan and Katie. Oh, poor Morgan! And cells with value McQueen aren't merged anymore. The cell Doe is still merged because the order here is sequential:

Last name merged

This example uses only LAST_NAME column written 3 times, so it's unclear what are these three arguments needed for.

Why did I decide that Morgan and Katie McQueen are married? Equivalence of last names doesn't necessarily mean that. The McQueen last name is quite frequent. We need to distinguish the families by some identifier. It would be more reliable.

IModelFieldGroup groups[] = new IModelFieldGroup[] {
  new ModelField( "USER_ID", "User identifier" ),
  new ModelField( "FAMILY_ID", "Family identifier" )
    .withVisible( false ),
  new ModelFieldGroup( "NAME", "Person name" )
    .withChild( new ModelField( "FIRST_NAME", "First name" ) )
    .withChild( new ModelField( "LAST_NAME", "Last name" ) ),
  new ModelField( "PHONE", "Phone number" )
};

We have added a new field FAMILY_ID and have hidden it (usually, end-users don't need to see the database internal identifiers). Let's fill the values:

for ( int i = 0; i < rows.length; i++ )
  data.setValue( i, "FAMILY_ID", i );

Every person has a separate family now. Let's define the real families. Let Doe be one family and McQueen be just a coincidence.

data.setValue( 1, "FAMILY_ID", 0 );

Redefine our span model:

table.setUI( new JBroTableUI()
  .withSpan( new ModelSpan( "FAMILY_ID", "LAST_NAME" ).withColumns( "LAST_NAME" ) ) );

Now cells would be merged only when the FAMILY_ID value of their row matches.

Last name merged by id

So, the first argument of the ModelSpan constructor is the identifier field. If the value of identifier field of current row equals to the value of identifier field of the next row then the cells of these rows would be merged.

What is the second argument needed for? Try to replace it with FIRST_NAME:

table.setUI( new JBroTableUI()
  .withSpan( new ModelSpan( "FAMILY_ID", "FIRST_NAME" ).withColumns( "LAST_NAME" ) ) );

It defines what value should be shown in the merged cells:

Last name merged by id with first name caption

Usually, second argument (merged cells caption) should have value that depends on the identifier: the identifier itself or a literal description of the identifier. For instance, LAST_NAME strongly depends on FAMILY_ID. Two rows with the same FAMILY_ID would definitely have the same LAST_NAME. The opposite is not always true.

This restriction isn't obligatory. You can see it in our example: FIRST_NAME doesn't depend on FAMILY_ID. Two rows with the same FAMILY_ID may have different FIRST_NAMEs (Jane and John Doe). If this restriction is violated then the value of merged cell may differ depending on rows order. Try to sort our last example by FIRST_NAME. The value of merged cell John changes to Jane on sorting. Use this restriction to avoid ambiguity.

At last, what do the arguments in method withColumns mean? Let's try this snippet:

table.setUI( new JBroTableUI()
  .withSpan( new ModelSpan( "FAMILY_ID", "LAST_NAME" ).withColumns( "LAST_NAME", "PHONE" ) ) );

And we get the following picture:

Last name and phone merged by id

This variable length argument (vararg) defines which columns should be merged according to this rule. So, we have merged cells of LAST_NAME and PHONE columns for the rows having the same FAMILY_ID. And the value of merged cells equals to the value of LAST_NAME column.

Try to drag the LAST_NAME or PHONE column and move it to the left. A merged cell is now divided by FIRST_NAME column. And it isn't merged anymore (except the Doe rows).

Last name and phone merged by id and divided

Take the full source code of this tutorial here.

Consider viewing an advanced span cells tutorial also.