It's a bit hard to say if this is a bug or not.
I think this happens any time when children are added during layout.
Most controls will avoid doing this, but when Cells are involved, there
is another layer of indirection. I think the View classes manage their
cells during layout, and since Cells can be newly created or the Cells
are modified in ways that would trigger a child to be added/removed,
then this effectively is changing the children list during layout.
For example, Labels, when you add a graphic, immediately update their
children list to include the graphic. Normally, calling `setGraphic` is
not done by you during layout but in an event listener or during
creation of the control. However, when it is called in
`Cell#updateItem` then layout is running, and the Label will change its
children list when it notices the graphic changed. This new child won't
have CSS applied yet.
I couldn't find documentation that says you shouldn't add/remove
children during layout, but I'm pretty sure this is standard practice.
The View controls may or may not be creating cells during layout, but
they are almost certain to call `updateItem` on cells during layout. I'm
not certain if this could be avoided in a similar fashion as I've done
for my own ListView skin (using the pulse listener); if it can be, then
this could be a bug fix. If it can't, then I think `Cell#updateItem`
should mention that the call should not modify the children list
directly or indirectly.
As I pointed out in my other reply, you may be able to set a dummy
graphic on all cells, and just hide it when it is not needed.
--John
On 22/02/2023 02:26, Scott Palmer wrote:
Well I managed to work around it by placing the applyCss() call on the
Cell itself, rather than the Shape node that I was styling. I'm still
not sure if I'm doing something wrong, if this is just how it's done
and the applyCss() call is the correct thing to do for this case, or
if this should be considered a bug.
Scott
On Tue, Feb 21, 2023 at 7:37 PM Scott Palmer <swpal...@gmail.com> wrote:
I agree, this seems like it could be the same issue. Could you
give me a little more context as to how this code works? I'm not
implementing my own skin (maybe I should, I've never tried that
before). Is this something I can trigger without a custom skin?
Regards,
Scott.
On Tue, Feb 21, 2023 at 12:01 PM John Hendrikx
<john.hendr...@gmail.com> wrote:
I've run into something similar, maybe even the same issue.
My issue was that modifying the list of cell children (in a
Skin for ListView) caused a 1 frame white flicker (my
application is black, so it was annoying) because no CSS was
applied yet to those newly added children. This occured when
the ListView first only had a few children and wasn't
completely filled, and then switching to a list which required
more cells to be created. I couldn't find a "correct" place to
modify the list of children that would avoid the flicker
(layoutChildren/computePrefWidth are places I tried).
In the end I did this, and added a comment to remind me why
the hack was there:
/*
* A pre-layout pulse listener is added to the current
Scene to manage the
* cells before the CSS pass occurs (this could also be
done with an AnimationTimer).
*
* If cells are not managed before the CSS pass, new cells
will be rendered for
* one frame without CSS applied. This results in a visual
artifact (a white flash
* for example if the background is supposed to be dark,
while white is the default
* color without any CSS applied).
*/
private final Runnable pulseListener = () -> {
int lines = vertical ? visibleColumns.get() :
visibleRows.get();
int firstIndex = (int)(scrollPosition.get()) * lines;
content.manageCells(firstIndex);
};
Now, the reason I think this may be same issue is that you're
also doing a modification of the children list during layout:
setting the graphic is sort of equivalent to
label.getChildren().add(graphic)
--John
On 20/02/2023 20:58, Scott Palmer wrote:
I'm seeing an odd issue with using CSS to colour a Shape when
I have it as a child of a TextFlow.
My use case is a "rich" text Cell in a tree or list. I want
to have the text portion in multiple colours and so instead
of using the graphic and text parts of a typical Cell, I'm
using only the Graphic and setting it to a TextFlow to get
the text colours. This means that I lose the ability to set a
graphic independen to the text, and so the graphic is also
added to the TextFlow.
To style the graphics, which are made of simple shapes, I'm
using CSS. It makes it easy to adapt the colours for when a
cell is selected etc. What I've noticed is that as the cell
selection moves around the Shape component of the TextFlow
flickers, as if it is drawn first using default colours and
then the CSS is applied afterward. I tried working around
this by explicitly calling applyCss() on the Shape from the
Cell's update method, but it did not help. If I explicitly
set the Stroke and Fill via the Shape API the flickering does
not occur. If I use CSS, but set the Shape as the Cell's
graphic and resort to only having plain text, there is no
flickering.
A test program that demonstrates this is below.
Bug or known limitation?
Scott
------------------------------------
package bugs;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
public class CSSFlickerInTextFlow extends Application {
public static void main(String[] args) {
launch(args);
}
private CheckBox onlyCssCB;
@Override
public void start(Stage stage) throws Exception {
ListView<String> list1 = new ListView<>();
ListView<String> list2 = new ListView<>();
var items1 = list1.getItems();
var items2 = list2.getItems();
for (int i = 1; i < 23; i++) {
var x = "Item #"+i;
items1.add(x);
items2.add(x);
}
list1.setCellFactory(t -> new MyCell1());
list2.setCellFactory(t -> new MyCell2());
onlyCssCB = new CheckBox("Use only CSS (shapes will
flicker in TextFlow)");
HBox buttons = new HBox(8, onlyCssCB);
buttons.setPadding(new Insets(4));
Tab custom = new Tab("Everything in Graphic", list1);
Tab withoutColoredText = new Tab("Graphic + Text",
list2);
TabPane tabs = new TabPane(custom, withoutColoredText);
VBox root = new VBox(
new Label("""
Focus in list, cursor up and down,
pay attention to the circle.
Try the same with the box checked -
circle flickers.
Doesn't happen when not using the
TextFlow.
"""),
buttons, tabs);
root.setPadding(new Insets(4));
var scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Flickering with CSS in TextFlow");
stage.show();
}
private Shape makeGraphic() {
Shape graphic = new Circle(6);
if (!onlyCssCB.isSelected()) {
graphic.setFill(Color.SALMON); // CSS overrides
these but causes flickering if not the same
graphic.setStroke(Color.BLACK);
}
graphic.setStyle("-fx-fill: salmon; -fx-stroke: black;");
return graphic;
}
class MyCell1 extends ListCell<String> {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setText(null);
if (!empty && item != null) {
TextFlow flow = new TextFlow();
Shape graphic = makeGraphic();
graphic.setTranslateY(2);
var nodeList = flow.getChildren();
Text name = new Text(item + " : ");
name.setStyle("-fx-fill:
-fx-text-background-color;");
Text extra = new Text("with Color");
extra.setStyle("-fx-fill:ladder(-fx-background, white 49%,
salmon 50%);");
nodeList.add(graphic);
nodeList.add(new Rectangle(4,0)); // gap
nodeList.add(name);
nodeList.add(extra);
setGraphic(flow);
} else {
setGraphic(null);
}
}
}
class MyCell2 extends ListCell<String> {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty && item != null) {
setGraphic(makeGraphic());
setText(item);
} else {
setText(null);
setGraphic(null);
}
}
}
}