I am using FlexboxLayout that I setup like:
layoutManager.flexDirection = FlexDirection.ROW
layoutManager.justifyContent = JustifyContent.FLEX_START
layoutManager.flexWrap = FlexWrap.WRAP
and the view holder's bind:
fun bind(n: Long) {
number.text = n.toString()
val lp: ViewGroup.LayoutParams = number.getLayoutParams()
if (lp is FlexboxLayoutManager.LayoutParams) {
(number.layoutParams as FlexboxLayoutManager.LayoutParams).flexGrow = 1.0f
}
}
For less than a single row this looks like what I want:
However it starts evenly spacing everything in the second row:
How can I make it continue to left-justify new items on the second row?
Related
when ever I build my project I got this error
here is the kotlin class code
var textBitmap: Bitmap? = null
dynamicItem.dynamicText[imageKey]?.let { drawingText ->
dynamicItem.dynamicTextPaint[imageKey]?.let { drawingTextPaint ->
drawTextCache[imageKey]?.let {
textBitmap = it
} ?: kotlin.run {
textBitmap = Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888)
val drawRect = Rect(0, 0, drawingBitmap.width, drawingBitmap.height)
val textCanvas = Canvas(textBitmap)
drawingTextPaint.isAntiAlias = true
val fontMetrics = drawingTextPaint.getFontMetrics();
val top = fontMetrics.top
val bottom = fontMetrics.bottom
val baseLineY = drawRect.centerY() - top / 2 - bottom / 2
textCanvas.drawText(drawingText, drawRect.centerX().toFloat(), baseLineY, drawingTextPaint);
drawTextCache.put(imageKey, textBitmap as Bitmap)
}
I couldn't figure out how to fix it
Instead of doing nested let like that, i would prefer to do some guard clause
val drawingText = dynamicItem.dynamicText[imageKey] ?: return // or you could assign an empty string `?: "" `
val drawingTextPaint = dynamicItem.dynamicTextPaint[imageKey] ?: return
val textBitmap: Bitmap = drawTextCache[imageKey] ?: Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888).applyCanvas {
val drawRect = Rect(0, 0, drawingBitmap.width, drawingBitmap.height)
val fontMetrics = drawingTextPaint.getFontMetrics()
val top = fontMetrics.top
val bottom = fontMetrics.bottom
val baseLineY = drawRect.centerY() - top / 2 - bottom / 2
drawingTextPaint.isAntiAlias = true
drawText(drawingText, drawRect.centerX().toFloat(), baseLineY, drawingTextPaint);
}
drawTextCache.put(imageKey, textBitmap)
Basically Kotlin can't smart cast textBitmap to a non-null Bitmap inside that lambda. You're probably getting the error on the Canvas(textBitmap) call, which can't take a null parameter, and the compiler can't guarantee textBitmap isn't null at that moment.
It's a limitation of lambdas referencing external vars which can be changed - I think it's because a lambda could potentially be run at some other time, so no guarantees can be made about what's happening to that external variable and whether something else could have modified it. I don't know the details, there's some chat here if you like.
The fix is pretty easy though, if all you're doing is creating a textBitmap variable and assigning something to it:
// Assign it as a result of the expression - no need to create a var first and keep
// changing the value, no need for a temporary null value, it can just be a val
val textBitmap: Bitmap? =
dynamicItem.dynamicText[imageKey]?.let { drawingText ->
dynamicItem.dynamicTextPaint[imageKey]?.let { drawingTextPaint ->
drawTextCache[imageKey]
?: Bitmap.createBitmap(drawingBitmap.width, drawingBitmap.height, Bitmap.Config.ARGB_8888).apply {
val drawRect = Rect(0, 0, drawingBitmap.width, drawingBitmap.height)
val textCanvas = Canvas(this)
drawingTextPaint.isAntiAlias = true
val fontMetrics = drawingTextPaint.getFontMetrics();
val top = fontMetrics.top
val bottom = fontMetrics.bottom
val baseLineY = drawRect.centerY() - top / 2 - bottom / 2
textCanvas.drawText(drawingText, drawRect.centerX().toFloat(), baseLineY, drawingTextPaint);
drawTextCache.put(imageKey, this)
}
}
}
I'd recommend breaking the bitmap creation part out into its own function for readability, and personally I'd avoid the nested lets (because it's not immediately obvious what you get in what situation) but that's a style choice
In my vaadin 7 application I have a CSSLayout that contains a Tabsheet. The Tabsheet contains a horizontalLayout. The HorizontalLayout contains a Table.
The table have a varying number of columns (between 3 and 20, changes upon request).
I want this table to occupy all available space both horizontally and vertically.
Vertically however the table should not extend beyond the the screen. So in case of having more rows than it can display the table should have a vertical scrollbar. (That is the case if I set the number of rows in table.setPageLength(), however I want to achieve this without setting explicit rownumbers, because I want the table to occupy all available space regardless of screensize, etc...)
Horizontally I also want a scrollbar if there are more columns then we have space for.
If I leave everything (csslayout, tabsheet, horizontallayout, table) default, I get the scrollbars, but I get a lot of space unused.
If I use setSizeFull() on tabsheet, horizontallayout, table then I get no unused space, however I lose the horizontal scrollbar and I can't ever reach end of the table with the vertical scrollbar.
Any help is appreciated.
EDIT -- UPDATE -- EDIT -- UPDATE --EDIT -- UPDATE --EDIT -- UPDATE
Here is a sample code. On my screen it's impossible to scroll down to the last row of the table. (and equally impossible to use the horizontal scrollbar)
#Override
protected void init(#SuppressWarnings("unused") VaadinRequest request) {
CssLayout css = new CssLayout();
HorizontalLayout upper = new HorizontalLayout();
OptionGroup first = new OptionGroup();
first.addItem("AAA");
first.addItem("BBB");
first.addItem("CCC");
first.addItem("DDD");
first.addItem("EEE");
first.addItem("Whatever");
upper.addComponent(first);
css.addComponent(upper);
HorizontalLayout hl = new HorizontalLayout();
hl.setMargin(true);
hl.setSpacing(true);
IndexedContainer c = new IndexedContainer();
for (int i = 0; i < 40; i++)
c.addContainerProperty("name" + i, String.class, "name" + i);
Table table = new Table("Test table", c);
for (int i = 0; i < 100; i++) {
Integer id = (Integer) c.addItem();
c.getItem(id).getItemProperty("name0").setValue(String.valueOf(i));
}
hl.addComponent(table);
TabSheet tab = new TabSheet();
tab.addTab(hl, "Table");
css.addComponent(tab);
hl.setSizeFull();
table.setSizeFull();
tab.setSizeFull();
css.setSizeFull();
setContent(css);
}
Maybe you don't set the size full on the css layout or maybe there are some trouble with styles.
It's better posting some code in questions like Why my code dosen't work?. However I wrote a simple test following your description and work as expected.
Edit
Try with VerticalLayout instead CssLayout
public class TestTableApp extends UI {
#Override
protected void init(VaadinRequest request) {
VerticalLayout css = new VerticalLayout();
HorizontalLayout upper = new HorizontalLayout();
OptionGroup first = new OptionGroup();
first.addItem("AAA");
first.addItem("BBB");
first.addItem("CCC");
first.addItem("DDD");
first.addItem("EEE");
first.addItem("Whatever");
upper.addComponent(first);
css.addComponent(upper);
HorizontalLayout hl = new HorizontalLayout();
hl.setMargin(true);
hl.setSpacing(true);
IndexedContainer c = new IndexedContainer();
for (int i = 0; i < 40; i++)
c.addContainerProperty("name" + i, String.class, "name" + i);
Table table = new Table("Test table", c);
for (int i = 0; i < 100; i++) {
Integer id = (Integer) c.addItem();
c.getItem(id).getItemProperty("name0").setValue(String.valueOf(i));
}
hl.addComponent(table);
TabSheet tab = new TabSheet();
tab.addTab(hl, "Table");
css.addComponent(tab);
hl.setSizeFull();
tab.setSizeFull();
table.setSizeFull();
css.setSizeFull();
// this do the trick
css.setExpandRatio(upper, 0);
css.setExpandRatio(tab, 1);
setContent(css);
}
}
If I want to add a row of text fields programatically in JavaFx, i can simply use the gridpane add method
This adds a set of text fields to row 1.
for (int i = 0; i < Fields.size(); i++) {
gridpane.add(new TextField(), i, 1);
}
Similarly, How do I delete a row?. I dont find a suitable method to delete a row/column conveeniently in JavaFX.
There's no directly equivalent method. To remove nodes, just use
gridpane.getChildren().remove(...); or gridpane.getChildren().removeAll(...); and pass in the nodes you want to remove from the pane.
In Java 8+, you can use removeIf:
gridPane.getChildren().removeIf(node -> GridPane.getRowIndex(node) == rowNumber);
Caveat
If removing items from the 0th row, also check GridPane.getRowIndex(node) == null, i.e.,
node -> GridPane.getRowIndex(node) == null || GridPane.getRowIndex(node) == 0
(I think this is JavaFX leaving the row number as null when no row number is given in the corresponding element in FXML, even though giving no row number in FXML means the element is in the 0th row, since the default row is the 0th row.)
This works pretty well:
while(MainGridPane.getRowConstraints().size() > 0){
MainGridPane.getRowConstraints().remove(0);
}
while(MainGridPane.getColumnConstraints().size() > 0){
MainGridPane.getColumnConstraints().remove(0);
}
JavaFX APIs are pretty lacking (like easily removing rows from GridPane) and unintuitive (like returning null instead 0 for GridPane.getRowIndex). Here is solution I came up with:
Utils:
package io.github.againpsychox.javaspeedrunsapp.utils;
import javafx.scene.Node;
import javafx.scene.layout.GridPane;
public class GridPaneUtils {
/**
* Gets row index constrain for given node, forcefully as integer: 0 as null.
* #param node Node to look up the constraint for.
* #return The row index as primitive integer.
*/
public static int getRowIndexAsInteger(Node node) {
final var a = GridPane.getRowIndex(node);
if (a == null) {
return 0;
}
return a;
}
/**
* Removes row from grid pane by index.
* Note: Might not work correctly if row spans are used.
* #param grid Grid pane to be affected.
* #param targetRowIndexIntegerObject Target row index to be removed. Integer object type, because for some reason `getRowIndex` returns null for children at 0th row.
*/
public static void removeRow(GridPane grid, Integer targetRowIndexIntegerObject) {
final int targetRowIndex = targetRowIndexIntegerObject == null ? 0 : targetRowIndexIntegerObject;
// Remove children from row
grid.getChildren().removeIf(node -> getRowIndexAsInteger(node) == targetRowIndex);
// Update indexes for elements in further rows
grid.getChildren().forEach(node -> {
final int rowIndex = getRowIndexAsInteger(node);
if (targetRowIndex < rowIndex) {
GridPane.setRowIndex(node, rowIndex - 1);
}
});
// Remove row constraints
grid.getRowConstraints().remove(targetRowIndex);
}
}
Example usage:
GridPaneUtils.removeRow(this.grid, GridPane.getRowIndex(this.idTextField));
Posting my solution for further readers...
I have a string with 1000 length. I need to split it and assign it to different controls. I do not have any character separator.
Since each string that I am assigning to controls do not contain same length. As of now I am doing it using substring in which i am specifying the length. But its becoming hectic for me as the length is huge.
Please suggest me is there any way to split and assign in simpler way?
You can use this string constructor:
var input = "Some 1000 character long string ...";
var inputChars = input.ToCharArray();
control1.Value = new string(inputChars, 0, 100); // chars 0-100 of input
control2.Value = new string(inputChars, 100, 20); // chars 100-120 of input
control3.Value = new string(inputChars, 120, 50); // chars 120-170 of input
...
Or using Substring:
var input = "Some 1000 character long string ...";
control1.Value = input.Substring(0, 100); // chars 0-100 of input
control2.Value = input.Substring(100, 20); // chars 100-120 of input
control3.Value = input.Substring(120, 50); // chars 120-170 of input
You could also do this
var parts = new []
{
Tuple.Create(0, 100),
Tuple.Create(100, 20),
Tuple.Create(120, 50),
}
var inputParts = parts.Select(p => input.Substring(p.Item1, p.Item2))
.ToArray();
control1.Value = inputParts[0];
control2.Value = inputParts[1];
control3.Value = inputParts[3];
This makes it much easier to maintain as the number of controls grows larger. You can store this list of 'parts' statically, so it can be reused elsewhere in your application without duplicating the code.
If all the controls the same type, you can do this:
var parts = new []
{
new { control = control1, offset = 0, length = 100 },
new { control = control2, offset = 100, length = 20 },
new { control = control3, offset = 120, length = 50 },
}
foreach(var part in parts)
{
part.control.Value = new string(inputChars, part.offset, offset.length);
// or part.control.Value = input.Substring(part.offset, offset.length);
}
You won't get around specifying the information which control gets which part of the string. Once you have this information (let's say they are stored in a control array controls and int array length), you can just loop over the string and do a piecewise Substring:
var controls = { control1, control2, control3, ... };
var lengths = { 100, 20, 5, ... };
int offset = 0;
for (int i = 0; i < controls.length; i++) {
controls[i].Value = myLongString.Substring(offset, lengths[i]);
offset += lengths[i];
}
Obviously, this will fail horribly if myLongString is shorter than the sum of all lengths or the array length of lengths is shorter than the one of controls, but adding some checks for that and throwing a suitable error is left as an exercise to the reader. In addition, the controls must be compatible in the sense that they all derive from the same base class with a common Value property. If that is not the case, you might have to do a few type checks and casts inside the loop.
How to select random item from a combo box, without selecting what is there already in the combobox.
I guess you want something like this:
Random random = new Random();
int newIndex = -1;
do {
newIndex = random.Next(comboBox.Items.Count);
} while (newIndex == comobBox.SelectedIndex && comboBox.Items.Count > 1);
comobBox.SelectedIndex = random.Next(comboBox.Items.Count);
Basically Combo box has items in string so if you can describe me some clear then we may help more anyway here is sample code
n you can do it
ComboBox b = new ComboBox();
Random rt = new Random();
string myText = "";
myText = b.Items[rt.Next(0, b.Items.Count - 1)].ToString();
You should use the Random class to get a random number between 0 and the max amount of items in the combobox. You should get this number repeatedly until you get one that doesn't match what is already selected within the combobox, like so:
Random random = new Random();
int newSelectedIndex = comboBox.SelectedIndex;
while (newSelectedIndex == comboBox.SelectedIndex) {
newSelectedIndex = random.Next(0, comboBox.Items.Count);
}
comboBox.SelectedIndex = newSelectedIndex;
// Item
// comboBox.Items[newSelectedIndex];
This may not work C/P'd, as I wrote it from the top of my head and don't have an IDE to test right now, but I hope you get the idea.
IMPORTANT: If you only have 1 item which is also selected, this may get into an endless loop...