Dynamic columns for JTreeTable - java

I am building a JTreeTable. I found some starter code and have come pretty far. In the end my goal is to be able to have different data at different levels like a hierarchical list.
Currently, I have it working with data at different levels. However, I am running up against a wall when it comes to changing the columns as a next goal. From where I currently stand I have 3 more milestones:
Show different set of columns for different levels
Ability to adjust column widths for different levels
Ensure the JTree part of the table always stays to left
I am getting close to closing out this task but again stuck at the first of these 3.
Since creating a JTreeTable is complex, the minimum example leverages several class listed below in the image:
I am happy to post the code to any of those classes but I also did not want clog the question with useless code. First let me show the functionality I want.
The first image is when the top level is selected and the second image is when the second level is selected. Notice how the columns are different. That is what I want to happen in my application.
Top level selected:
Second level selected:
So one way I tried to solve this problem, is when the list selection is changed inside this section of code:
ListSelectionListener listener = (ListSelectionEvent e) -> {
TreeTableModelAdapter adapter = (TreeTableModelAdapter) JTreeTable.this.getModel();
//Need to see why this breaks.
JTreeTable.this.getTableHeader().setColumnModel(adapter.getColumnModel());
};
this.getSelectionModel().addListSelectionListener(listener);
This code is in the initialization of the JTreeTable. I have tried setting the column model on both the TableHeader and the table as well. Below is what happens then when I select a row:
The columns just disappear on me. The creation of the column model is happening in the TreeTableModelAdapter class with the following method:
public TableColumnModel getColumnModel(){
DefaultTableColumnModel model = new DefaultTableColumnModel();
for(int i=0;i<getColumnCount();i++){
TableColumn column = new TableColumn();
column.setIdentifier(getColumnName(i));
model.addColumn(column);
}
return model;
}
Any direction would be very helpful. Again happy to post any code you think could be helpful to answer the question. Just put a comment in and I will add it right away.

I will add the milestones as I find them in case this helps others, but for now this question is answered.
Milestone 1
I was actually able to solve the first milestone. The key is to trigger the creation of the columns of the column model, not to create a new column model. Below is the code for when the row selection is changed:
//Change columns depending on row
ListSelectionListener listener = (ListSelectionEvent e) -> {
createDefaultColumnsFromModel();
};
this.getSelectionModel().addListSelectionListener(listener);
This code creates the columns based on the row selected in the JTree part of the JTreeTable. The TreeTableModelAdapter implements the getColumnCount() and getColumnName() methods by also passing the selected row in the JTree to the JTreeTableModel so that the columns and their names are dynamically retrieved based on a particular node in the JTree. The key for this for me was trigger those to be called again to update the JTreeTable.
Milestone 2
Adjusting column widths based on the data level proved to be much more difficult than I had originally anticipated. In order to retain the cells state when the column model changed I had to disconnect the painting of the cells from it. This is a hairy process because this is done inside BasicTableUI and the method that gets the rectangle of the cell is private. So I had to subclass it, overload the paint() method and create my own methods that get called inside the paint method. There was a lot of copy pasting so that I could call normally private methods. I just renamed them and referenced these methods instead. The way the ui class was designed did not make it very flexible. Below is 2 images where I am selecting different levels and the columns are obviously different widths at different levels.
Milestone 3
I was able to make this work by keeping track of the view in the model. This seems very dirty to me as the model should separated from the view. Since the tree column's class is unique, I just returned the right class if that column was the first in the view.
The one problem I have with this technique is that I get unexpected behavior where the value returned is not consistent. I attempted to resolve this by overriding JTree.covertValueToText(). Since a JTree only expects 1 value and depending on the sequence of columns in the view this value could change. So in overriding this method I check the stored index for the JTree column's value. Again this causes the unexpected behavior. I will update the post if I find the fix.

Related

JTable and its Relationship with the Data Source

I'm building a program that gathers a couple lists of files that match a particular set of criteria and manipulates them as it appropriate depending on the source, type of file, etc... My hope is that it will find the files and display them in a list that is easy to read. The user will select which files are going to be processed from the list, then hit a button that "starts the commotion," if you will.
Right now, I've made a class called DrawingFile that looks like:
class DrawingFile {
private static String fileName, fileType;
private static boolean actionable;
private static Path filePath;
public DrawingFile (Path path){
setFilePath(path);
setFileName(stripExtension(path));
setFileType(getExtension(path));
setActionable(true);
}
...(methods omitted to save time)...
My plan, initially was to create a JTable that populates based on a List of DrawingFiles with each of the fields in the objects being a column in the table, except the Path, which would not be displayed on the table. From there, the user would click a checkbox which would determine whether or not a file is going to be manipulated.
My issues stem first and foremost from my being relatively new to programming. This is the first program I've written that people are going to use, that also has any sort of UI.
As such my questions are:
Does my plan above make any sense at all?
Would it make more sense to leave the table out of it and create a series of JPanels inside a container? (this seems like it wouldn't be best practice)
If I do go with the table, should I scrap the DrawingFile class and store the data in the TableModel?
3a. If so, is there a way I can hide the Path in the table?
How do I go about changing the actionable boolean when it is (un)checked on the table?
On a scale of 1-10, how badly am I overthinking this?
If I understand correctly, you have a list of DrawingFile objects, and want to display this list as a JTable, where each row represents an object of the list. Yes, that makes perfect sense.
No. A table is perfect for that.
No. You should create a custom DrawingFileTableModel class, extending AsbtractTableModel, and using the list of objects as the source to implement the method. Google for "Java tutorial JTable", and you'll find an example in the official tutorial.
By making sure that isCellEditable() returns true for that column and row, and by implementing the setValueAt() and getColumnClass() methods correctly. The javadoc and the tutorial are your friends here. setValueAt(), when called with the index of the boolean column, should set its new value in the DrawingFile stored at the given row index in the backing list. getColumnClass(), when called with the index of the boolean column, should return Boolean.class.

JUnit-testing of NatTable

I want to do plain UI testing (i.e., not using SWTBot or other UI test frameworks) of NatTable contents.
My approach is to create a shell, add my custom NatTable and then access the cell and check its contents (data value, config label etc.):
// Note: this is Xtend code
#Before
def void setup()
{
shell = new Shell(Display.getCurrent)
shell.layout = new FillLayout
parent = new Composite(shell, SWT.NONE)
parent.layout = new GridLayout
fixture = new MyNatTableViewer(parent) // this is my custom nattable impl under test
shell.pack
shell.visible = true
}
#Test
def void testLabel()
{
assertCellLabel(2, 2, "test-label");
}
def assertCellLabel(int row, int col, String expected)
{
val labels = parameterTable.getCellByPosition(col, row)?.configLabels
assertThat(labels).describedAs("Labels for row " + row + " col " + col).isNotNull
assertThat(labels.labels).describedAs("Labels for row " + row + " col " + col).contains(expected)
}
To test my other components it was enough to just create the shell and the parent composite; packing and setting visible was not required for my tests to work.
Yet, with NatTable, getCellByPosition() returns null if the cell is not visible - so I added the code to pack and set the shell visible. This works for small tables (with 2 rows and a few columns).
Sadly, it does not work for large tables. I suspect this is because the viewport layer does not create cells which are not in the visible area (which is, I know, the strength of NatTable - that it only creates the required structures on demand). This is, of course, desired for normal runtime behavior.
But is there a(nother) way to get the cell in a guaranteed way (in other words, can I make the NatTable/ViewportLayer believe that the cell is visible so I don't get null as long as the cell exists content-wise?)
I could, of course, test my label accumulators, data providers etc. directly, but I wanted to approach this more from a black-box point of view here.
That question is contradictory in itself. You are asking for a black box approach for testing NatTable, but you want to change the behavior of NatTable on testing. That is not a black box approach!
If you really want to test with a black box approach, you need to ensure that the cell is rendered. This can be done by triggering scrolling, e.g. by executing the ShowCellInViewportCommand. That is the real black box approach, because returning null for a non-visible cell is the correct result.
If you need something in between a real black box approach and an approach that makes use of internal knowledge (which you are asking for) you have to ways to get there.
Operate on a layer below the ViewportLayer. Typically the SelectionLayer can be used. But of course this doesn't need to mean anything, because the layer stack can differ from setup to setup. The ViewportLayer is the one that introduces the virtual nature to a NatTable and the scrolling ability. It avoids the access to the underlying layers. So asking one of these will return the value you expect.
Disable the ViewportLayer by executing the TurnViewportOffCommand. This is basically a hack and could trigger additional things in the back that you might not want. But I have seen that suggestion in other contexts and therefore want to name it here. I don't suggest to use it anyway!
Note that both approaches are more like hacks when we are talking about black box testing, because you are making assumptions to the composition. They can not be applied in general because of the various configuration abilities.
Regarding the hidden question about why you need to set the Shell visible. Well basically because the SWT events for painting and resizing need to be triggered in order to start the size calculations and printing of NatTable correctly according to the Shell state. In our examples (which are also plain SWT) we call Shell#open().
And as a last comment on your implementation, I don't understand why you are sub-classing NatTable. Our API was never intended to do that. I suppose you do this to do some static pre-configuration e.g. the layer stack. But personally I don't like that approach. Everytime someone extends our classes to override some internal methods it ends up in questions or bug reports because the behavior changes. But I think that is generally an issue of an open API to give developers the most possible flexibility on customization.
I am also trying to read data from invisible cells of nattable. I am trying to use ShowCellInViewportCommand as follows:
widget.doCommand(new ShowCellInViewportCommand(gridlayer.getBodyLayer(), column, row));
//where row is say 50 and column is 20 and the cell is invisible.
I also tried,
widget.doCommand(new ShowRowInViewportCommand(widget.getLayer(), row));
//here the default value given by nattable.getLayer is passed
widget corrosponds to nattable instance.
After the call, nothing happens in UI. The cell does not get displayed.
Do I need to do anything else?
How should I go to read invisible cells of nattable.

Multiple TableView Tables on single pane fail (JavaFX)

I have to build a page within a java application containing 4 tables (we've chose to use javaFX to use, therefore we're using TableView tables)
There is a problem though. Using multiple tables on 1 pane doesnt seem succesfull. Here you can see the result: http://i.imgur.com/lmPB6Ih.png?1 (I cannot post images yet..)
As you can see it only shows 1 player (a goalkeeper) instead of all the desired players. Whenever i disable all tables but one it shows the desired information.
You can find my code via this link: http://pastebin.com/5AAchCKh
You can't reuse the TableColumn objects.
This fails:
tableTeamField.getColumns().addAll(name,position,age,worth,shooting,...);
tableSelectionField.getColumns().addAll(name,position,age,worth,shooting,...);
When the data is applied to the tables, only the data from the last table is shown in all of them. Basically, you are applying to the sane column different values: only the last one will be visible.
So you need to create different TableColumns for each TableView. This will work:
tableTeamField.getColumns().addAll(name1,position1,age1,worth1,shooting1,...);
tableSelectionField.getColumns().addAll(name2,position2,age2,worth2,shooting2,...);

When to use convertRowIndexToModel

I have some JDialogs displaying JTables.
When the header columns are clicked a sort occurs on that column.
My question is : how can I know when a column header has been clicked and thus made a sort active.
When the sort is active, I know I should user the .convertRowIndexToModel method.
But how do I detect that a column is sorting in order not to mess the correct index if no sort is active?
Generally speaking, you should ALWAYS uses the convertRowIndexToModel when you take an index value from the view (JTable) and try and look up some value within the model. The JTable does this automatically when you use it's methods, but incase you're not, you need to take care of it yourself.
There's no need to know if the view is sorted or not...
If you "really" want to know when a table is sorted, you could attach a RowSorterListener to the TableRowSorter used by the table.
You could also use the TableRowSorter#getSortKeys to see which columns are included in the sort...
I have used it for my selection table. When the auto order is activated (setAutoCreateRowSorter(true))
the indices of the model table and the visual change, so you have to tell it to look for it within the model with respect to the one you are seeing.
((CustomTable)form.getAvailListView().getModel()).data.get(form.getListView().convertRowIndexToModel(i))

JTable with jgoodies sorting trouble

I've got a blocking problem with the sorting functionality of a JTable; this made stall the development of the spare time open source project for 4 months now. Hope to be pointed into the right direction here.
Context: I'm working on extending the functionality of the ps3mediaserver to add a media library with pms-mlx. The UI of the media server has been done using swing.
Problem: When clicking on a column header in the JTable, a seemingly random column gets sorted instead of the one having been clicked.
Current implementation: Here's the description of the different components and classes being used for the implementation:
ETable: As alternate row colours aren't supported by default in the JTable, I've switched to the ETable extending the JTable. Source comes from here
FileDisplayTable: This is the class creating the table. In the init() method, the sorting is being enabled with 'table.setAutoCreateRowSorter(true);'
FileDisplayTableCellRenderer: Exists to always align cell content on the left
FileDisplayTableColumnModel: Does some mapping between internal types and column names
FileDisplayTableAdapter: This class implements com.jgoodies.binding.adapter.AbstractTableAdapter to map the objects with the table columns.
Possible solutions:
Preferably, I'd like to keep the current implementation and figure out how to correct the sorting, but I doubt someone can help me out with that!? Additionally their are some bits of code I had to add because of strange behaviours; they're commented in the code
The alternate option would be to change the JTable for another control altogether. I've made some research but didn't find the solution I was hoping for. The constraints are that
it must be embeddable in a swing UI
preferably it should support data bindings
support alternate row colours
row sorting
At some point it will be possible to open an editing dialogue, where the content of the row has to be retrieved, can be edited and when saved the row has to be updated.
Before reworking the entire thing I'd like to be sure the component will be able to handle all I want to do with it.
I'm more used to create GUIs using .NET in Visual Studio. It's quite different and a lot more difficult to do the same with swing. Please show me I'm wrong :)
[edit] If someone is willing to reproduce the problem, either get the source or the binaries, launch the application, navigate to the media library tab. In the Genral section import some videos by adding some video files. Go to the library section, click on apply to refresh the list and try to sort the table.
It may be useful to know that JTable columns can be dragged by the user. As a result, the view (JTable or a subclass) and model (an implementation of TableModel) may have different column numbers. Similarly, a RowSorter may affect the order or number of rows in the view as compared to the model. The related conversion methods are mentioned in How to Use Tables: Sorting and Filtering. In particular: "When using a sorter, always remember to translate cell coordinates."
Addendum: As an alternative, consider org.netbeans.swing.etable.ETable or it's subclass org.netbeans.swing.outline.Outline, depicted here.

Categories