JavaFX TableView with two fixed-columns and last one taking available space - java

I'm trying to make a simple 3-column TableView:
One icon column containing a fixed size icon. This column must not be resizable, and must have a fixed size.
One text column with a predefined prefered size, which can be resized if needed.
One last column taking all available space.
Unfortunatly, this simple use case seems to be really complicated with Java FX 8. I tried the following, which should work according to my understanding of the documentation:
TableColumn<DebuggerItem, ImageView> iconColumn = new TableColumn<>("ICON");
TableColumn<DebuggerItem, String> typeColumn = new TableColumn<>("TEXT");
TableColumn<DebuggerItem, String> textColumn = new TableColumn<>("DATA");
setColumnResizePolicy(CONSTRAINED_RESIZE_POLICY);
// Fixed size column
iconColumn.setPrefWidth(40);
iconColumn.setMinWidth(40);
iconColumn.setMaxWidth(40);
iconColumn.setResizable(false);
// Predefined preferred size of 100px
typeColumn.setPrefWidth(100);
getColumns().addAll(iconColumn, typeColumn, textColumn);
This results in the following TableView:
We can see that if the first column has a correct size, the second and the hird have the same size, which is not what I expected. The second column should be 100px wide, and the last one take the rest of the space.
What did I miss ?

According to #kleopatra link, the solution is to use properties to compute last column width, and NOT use CONSTRAINED_RESIZE_POLICY :
LastColumnWidth = TableViewWidth - SUM(Other Columns Widths)
Which, according to my example give the following Java code:
TableColumn<DebuggerItem, ImageView> iconColumn = new TableColumn<>("ICON");
TableColumn<DebuggerItem, String> typeColumn = new TableColumn<>("TEXT");
TableColumn<DebuggerItem, String> textColumn = new TableColumn<>("DATA");
// Fixed size column
iconColumn.setPrefWidth(40);
iconColumn.setMinWidth(40);
iconColumn.setMaxWidth(40);
iconColumn.setResizable(false);
// Predefined preferred size of 100px
typeColumn.setPrefWidth(100);
// Automatic width for last column
textColumn.prefWidthProperty().bind(
widthProperty().subtract(
iconColumn.widthProperty()).subtract(
typeColumn.widthProperty()).subtract(2)
);
getColumns().addAll(iconColumn, typeColumn, textColumn);
Please note that we need to substract 2 pixels to get the exact width, it's not clear why.

I created a more general solution, that pixel-perfectly calculates width of last column for TableViews that:
have any number of columns
possibly hide or show columns at runtime
possibly have custom insets
possibly have scrollbar, possibly showing and hiding at runtime.
Usage:
bindLastColumnWidth(tableView);
Source:
public static <T> void bindLastColumnWidth (TableView<T> tableView) {
List<TableColumn<T,?>> columns = tableView.getColumns();
List<TableColumn<T,?>> columnsWithoutLast = columns.subList(0, columns.size() - 1);
TableColumn lastColumn = columns.get(columns.size() - 1);
NumberExpression expression = tableView.widthProperty();
Insets insets = tableView.getInsets();
expression = expression.subtract(insets.getLeft() + insets.getRight());
for (TableColumn column : columnsWithoutLast) {
NumberExpression columnWidth = Bindings.when(column.visibleProperty())
.then(column.widthProperty())
.otherwise(0);
expression = expression.subtract(columnWidth);
}
ScrollBar verticalScrollBar = getScrollBar(tableView, Orientation.VERTICAL);
if (verticalScrollBar != null) {
NumberExpression scrollBarWidth = Bindings.when(verticalScrollBar.visibleProperty())
.then(verticalScrollBar.widthProperty())
.otherwise(0);
expression = expression.subtract(scrollBarWidth);
}
expression = Bindings.max(lastColumn.getPrefWidth(), expression);
lastColumn.prefWidthProperty().bind(expression);
}
private static ScrollBar getScrollBar (Node control, Orientation orientation) {
for (Node node : control.lookupAll(".scroll-bar")) {
if (node instanceof ScrollBar) {
ScrollBar scrollBar = (ScrollBar)node;
if (scrollBar.getOrientation().equals(orientation)) {
return scrollBar;
}
}
}
return null;
}

You can call this below code after you add all columns to table.
This is nice when the code that resizes does not have a explicit list of columns.
public static void makeLastColumnGrow(TableView<?> items) {
var last = items.getColumns().get(items.getColumns().size() - 1);
var aBinding = Bindings.createDoubleBinding(() ->{
double width = items.getWidth();
for (int i = 0; i < items.getColumns().size() - 1 ; i++) {
width = width - items.getColumns().get(i).getWidth();
}
return width - 2; // minus -2 to account for border i think
}, items.widthProperty());
last.prefWidthProperty().bind(aBinding);
}

Related

MPChart xAxis values not aligned

I am trying to implement grouped MPAndroid Bar chart. I have a group of 2 datasets that i want to display. The problem is that the xaxis values are not center aligned with the bar chart (as per the screenshot). I checked other questions as well and implemented the following answers provided.
I want to make the labels center aligned with the grouped bars.
float barSpace = 0.02f;
float groupSpace = 0.3f;
int groupCount = 2;
data.setBarWidth(0.155f);
pvaAmount_chart.getXAxis().setAxisMinimum(0);
pvaAmount_chart.getXAxis().setAxisMaximum(0 + pvaAmount_chart.getBarData().getGroupWidth(groupSpace, barSpace) * groupCount);
pvaAmount_chart.groupBars(0, groupSpace, barSpace);
pvaAmount_chart.getXAxis().setCenterAxisLabels(true);
pvaAmount_chart.notifyDataSetChanged();
When entering groupcount=2 as 2 types of bars:
When entering groupcount=4 number of grouped charts:
i'm doing it like this :
String [] values = new String[insert the lenght of your array]
for (int blabla=0;i<values[lenght];blabla++){
String dayOfTheWeek = dateFormat.format(mydate);
vales[blabla] = dayOfTheWeek //in my case
}
xAxis.setGranularity(1.0f); //
xAxis.setCenterAxisLabels(false);
xAxis.setGranularityEnabled(true);
xAxis.setLabelCount(values.length,false); //
List<String> stringList = new ArrayList<String>(Arrays.asList(values));
xAxis.setGranularityEnabled(true);
xAxis.setLabelCount(values.length,false); //
xAxis.setValueFormatter(new IndexAxisValueFormatter(getXAxisValues((ArrayList<String>) stringList)));
i'm using : 'com.github.PhilJay:MPAndroidChart:v3.1.0'

Overflowing layout with TableLayout

I need to show three things in a row in a sort of table. The first column should have a fixed width of say 15% of the screen. The third one should be right aligned and take its preferred width. The second one should take all the remaining space (I'll need to add some spacing, but that's another story).
This happens in start:
final Container list = new Container(BoxLayout.y());
list.setScrollableY(true);
final String[][] lines = {
{"19", "Some text", "123,00"},
{"20", "Some very very very very looong text", "1,00"},
};
for(final String[] line : lines) list.add(createContainer(line));
form.add(list);
The container is rather trivial:
private Container createContainer(String[] line) {
final TableLayout tableLayout = new TableLayout(1, 3);
tableLayout.setGrowHorizontally(true);
final Container result = new Container(tableLayout);
{
final Label l = new Label(line[0]);
l.getAllStyles().setFgColor(0x0000FF);
result.add(tableLayout.createConstraint().widthPercentage(15), l);
}
{
final Label l = new Label(emptyToSpace(line[1]));
l.getAllStyles().setFont(Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
result.add(tableLayout.createConstraint().widthPercentage(-2), l);
}
{
final Label l = new Label(line[2]);
l.getAllStyles().setFont(Font.createSystemFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_LARGE));
l.getAllStyles().setFgColor(0x00FF00);
result.add(tableLayout.createConstraint().widthPercentage(-1).horizontalAlign(Component.RIGHT), l);
}
return result;
}
According to the javadoc, -1 means preferred size and -2 means "remaining space". It sort of works, but there seem to be a miscalculation.
The problem happens in the simulator, no matter what device I choose. I may be doing it all wrong, as I'm new to codenameone layouts.
The -2 flag is mostly optimized for the last column so this looks like a bug but might be hard to workaround. I don't see a need to use table layout here since you don't use one table which would provide alignment between the rows.
A simpler approach would be border layout e.g.:
Container c = BorderLayout.centerEastWest(new Label(emptyToSpace(line[1])),
rightText, leftText);
If you want the left column to align just use Component.setSameWidth() on the entire column.

NatTable - Percentage Sizing with Column hiding

I would like to use column percentage sizing to force the table to take on the width of the parent.
This does not work when I hide column(s) by default because the setColumnPercentageSizing() method does not seem to exclude hidden columns and does not correctly calculate the width.
Is there an easy way to adjust this in my code?
Example:
public void example(){
createGlazedListsGridLayer();
autoResizeColumns();
nattable.configure();
}
public GlazedListsGridLayer createGlazedListsGridLayer(){
SortedList<T> sortedList = new SortedList<>(eventList, null);
this.bodyDataProvider = new ListDataProvider<>(sortedList,
columnPropertyAccessor);
this.bodyDataLayer = new DataLayer(this.bodyDataProvider);
ColumnHideShowLayer columnHideShowLayer = new
ColumnHideShowLayer(bodyDataLayer);
// In this example, hide the first column
columnHideShowLayer.hideColumnPositions(Lists.newArrayList(0));
this.bodyLayerStack = new DefaultBodyLayerStack(new
GlazedListsEventLayer<>(columnHideShowLayer, eventList));
//...etc
}
protected void autoResizeColumns() {
glazedListsGridLayer.getBodyDataLayer().setColumnPercentageSizing(true);
nattable.addConfiguration(new DefaultNatTableStyleConfiguration() {
{
cellPainter = new LineBorderDecorator(new TextPainter(false,
true, 5, true));
}
});
}
UPDATE
It's not ideal but this is the closest I could get to it
public void adjustColumnWidth() {
getBodyDataLayer().setColumnPercentageSizing(false);
// Avoid the first column since it's hidden
for (int x = 1; x <= numColumns; x++) {
getBodyDataLayer().setColumnWidthByPosition(x,
getParent().getSize().x / numColumns, true);
}
}
UPDATE 2
Here are a couple of different things I tried in various combinations. None of them seem to keep the column hidden after a table is dynamically populated with data.
protected void enableAutoResizeColumns() {
getBodyDataLayer().setColumnPercentageSizing(true);
getBodyDataLayer().setDefaultColumnWidthByPosition(0, 0);
getBodyDataLayer().setColumnWidthByPosition(0, 0);
getBodyDataLayer().setColumnWidthPercentageByPosition(0, 0);
getNatTable().addConfiguration(new
DefaultNatTableStyleConfiguration() {
{
cellPainter = new LineBorderDecorator(new TextPainter
(false, true, 5, true));
}
});
}
Currently there is no solution for that. The reason for this is that the column widths are calculated in the DataLayer. The ColumnHideShowLayer sits on top of it and simply hides columns. It doesn't communicate back to the DataLayer that something is hidden.
In the end the ColumnHideShowLayer would need to re-trigger percentage size calculation based on the hidden state. But there is currently no API for that.
Feel free to create an enhancement ticket and provide a patch if you have an idea how to solve it.

Display text progressively with libgdx

I am looking for a way to display text progressively with libgdx, but I can't find a way to do it exactly the way I want. Here is what I did:
I have a text label that is being updated periodically to display a different text. The label is set to setWrap(true); and setAlignment(Align.center);
Every time I change the text of the label I use a custom Action which I built like this
public class DisplayTextAction extends TemporalAction{
private CharSequence completeText;
#Override
protected void update(float percent) {
((Label)actor).setText(
completeText.subSequence(
0,
(int)Math.round(completeText.length()*percent));
}
public void setText(String newText){
completeText = newText;
}
}
Every text update, I call the action from a pool, change the text and add the action to the label.
Here is my problem: This doesn't work the way I want with a centered and wrapped text.
This happens when text isn't centered (dots represent space):
|h........|
|hel......|
|hello....|
(Works as intended)
This is what happens when the text is centered:
|....h....|
|...hel...|
|..hello..|
And this is how I want it to behave:
|..h......|
|..hel....|
|..hello..|
My original idea to fix this was to use 2 sets of strings, one that is the visible text, and one invisible that acts as "padding". I came up with something like this:
CharSequence visibleText = completeText.subSequence(
0,
(int)Math.round(completeText.length()*percent));
CharSequence invisibleText = completeText.subSequence(
(int)Math.round(completeText.length()*percent),
completeText.length());
So I have my two sets of strings, but I can't find a way to display two different fonts (one visible, and another one which is the same but with an alpha of 0) or styles in the same label with Libgdx.
I'm stuck, I don't know if my approach is the right one or if I should look into something completely different, and if my approach is correct, I don't know how to follow it up using libgdx tools.
EDIT:
I followed Jyro117's instructions and I could make great progress, but I couldn't make it work with centred text on multiple lines.
imagine this text:
|all those lines are|
|..for a very long..|
|........text.......|
And it has to be displayed like this
|all th.............|
|...................|
|...................|
|all those line.....|
|...................|
|...................|
|all those lines are|
|..for a ve.........|
|...................|
|all those lines are|
|..for a very long..|
|........text.......|
Jyro117's solution give either
|all those lines are|
|for a very long....|
|text...............|
displayed correctly.
or
|...................|
|......all tho......|
|...................|
|...................|
|...all those lin...|
|...................|
|all those lines are|
|......for a v......|
|...................|
You are over-complicating the solution. All you really need is to determine the size of the label when all the text is added. Once you have determined that, lock the label size to those dimensions, put it inside of a table that expands to fill up the area around it, and then update your label with the action. (You can use a pool and such as needed, but for simplicity I left that out of the code below).
You will have to obviously adapt the code to yours, but this gives you a code reference to what I mean.
Here is a code snippet on one way to do it:
stage = new Stage(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
Gdx.input.setInputProcessor(stage);
uiSkin = new Skin(Gdx.files.internal("skin/uiskin.json"));
Table fullScreenTable = new Table();
fullScreenTable.setFillParent(true);
final String message = "hello";
final Label progressLabel = new Label(message, this.uiSkin);
final TextBounds bounds = progressLabel.getTextBounds(); // Get libgdx to calc the bounds
final float width = bounds.width;
final float height = bounds.height;
progressLabel.setText(""); // clear the text since we want to fill it later
progressLabel.setAlignment(Align.CENTER | Align.TOP); // Center the text
Table progressTable = new Table();
progressTable.add(progressLabel).expand().size(width, height).pad(10);
final float duration = 3.0f;
final TextButton button = new TextButton("Go!", this.uiSkin);
button.addListener(new ClickListener() {
#Override public void clicked(InputEvent event, float x, float y) {
progressLabel.addAction(new TemporalAction(duration){
LabelFormatter formatter = new LabelFormatter(message);
#Override protected void update(float percent) {
progressLabel.setText(formatter.getText(percent));
}
});
}
});
stage.addActor(button);
fullScreenTable.add(progressTable);
fullScreenTable.row();
fullScreenTable.add(button);
stage.addActor(fullScreenTable);
Edit:
Added code to center and top align text in label. Also added code to fill spaces on the end to allow for proper alignment. Note: Only useful for mono-spaced fonts.
class LabelFormatter {
private final int textLength;
private final String[] data;
private final StringBuilder textBuilder;
LabelFormatter(String text) {
this.textBuilder = new StringBuilder();
this.data = text.split("\n");
int temp = 0;
for (int i = 0 ; i < data.length; i++) {
temp += data[i].length();
}
textLength = temp;
}
String getText(float percent) {
textBuilder.delete(0, textBuilder.length());
int current = Math.round(percent * textLength);
for (final String row : data) {
current -= row.length();
if (current >= 0) {
textBuilder.append(row);
if (current != 0) {
textBuilder.append('\n');
}
} else {
textBuilder.append(row.substring(0, row.length() + current));
// Back fill spaces for partial line
for (int i = 0; i < -current; i++) {
textBuilder.append(' ');
}
}
if (current <= 0) {
break;
}
}
return textBuilder.toString();
}
}

paintitem and indexoutofbounds error

I have create a table with bars which shows frequency of the words in a text.I show the number of special word which user click on them or frequency of whole words in the text. I fetch my list of list and send it to the fill table function. All thing is OK but when I select a special word and then click to show whole words I get indexoutofbounds exception. I guess it is because I change my datasource. It is really strange but simple. However, I could not solve it.
public void fill_count_table(List<RootWordSet> source){
final List<RootWordSet> mysource=source;
if(source!=null){
for(int i=0;i<source.size();i++){
TableItem ti=new TableItem(count_table, SWT.NONE);
ti.setText(source.get(i).getRoot());
}
count_table.addListener(SWT.PaintItem, new Listener() {
public void handleEvent(Event event) {
if (event.index == 1) {
try{
GC gc = event.gc;
TableItem item = (TableItem)event.item;
int index = count_table.indexOf(item);
System.out.println(mysource.size());
int percent = mysource.get(index).getWordNumber();
org.eclipse.swt.graphics.Color foreground = gc.getForeground();
org.eclipse.swt.graphics.Color background = gc.getBackground();
gc.setForeground(Display_1.getSystemColor(SWT.COLOR_BLUE));
gc.setBackground(Display_1.getSystemColor(SWT.COLOR_DARK_BLUE));
int width = (tc2.getWidth() - 1) * percent / 100;
gc.fillGradientRectangle(event.x, event.y, width, event.height, true);
Rectangle rect2 = new Rectangle(event.x, event.y, width-1, event.height-1);
gc.drawRectangle(rect2);
gc.setForeground(Display_1.getSystemColor(SWT.COLOR_RED));
String text = Integer.toString(percent) ;
Point size = event.gc.textExtent(text);
int offset = Math.max(0, (event.height - size.y) / 2);
gc.drawText(text, event.x+2, event.y+offset, true);
gc.setForeground(background);
gc.setBackground(foreground);
}
catch(Exception e){
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
});
}
else{
count_table.removeAll();
count_table.redraw();
}
}
this is the line that make error: int percent = mysource.get(index).getWordNumber();
I really do not know what happens when I change datasources. when I shift to each other it stuck. Even I put a println to check the size of datasource but it was quite strange it had two size. One belong to former datasource and one belong to newwr. Anyway If I remove this graphic part table fill correctly. What do you think?
change this line:
int percent = mysource.get(index).getWordNumber();
to
int percent = mysource.get(index-1).getWordNumber();

Categories

Resources