I want to dynamically change colors of different type charts, using colorpicker. Then I made sample code like this for pie chart.
Color picker action listener:
colorPicker.setOnAction(event -> {
Node node = chart.lookup(".default-color0.chart-pie");
String str = "-fx-pie-color:" + toRGBCode(colorPicker.getValue()) + ";";
node.setStyle(str);
});
CSS file:
.default-color0.chart-pie { -fx-pie-color: #ffd700; }
.default-color1.chart-pie { -fx-pie-color: #ffa500; }
.default-color2.chart-pie { -fx-pie-color: #860061; }
.default-color3.chart-pie { -fx-pie-color: #adff2f; }
.default-color4.chart-pie { -fx-pie-color: #ff5700; }
It works fine but only partially. Problem is that, when I'm changing the color, the legend of chart doesn't follow it. How to dynamically update legend?
You can update the legend by obtaining it after it has been drawn via a node lookup and then modifying its style similar to what you have already done in the question
This answer is built on what you have provided so there is the assumption you know which item you want to update as you know the style class in this line:
Node node = chart.lookup(".default-color0.chart-pie");
So with that in mind, you can do something similar with the following:
public static void updateChartLegendColorFromItemName(Chart chart, String itemToUpdate, Color legendColor){
Set<Node> legendItems = chart.lookupAll("Label.chart-legend-item");
if(legendItems.isEmpty()){ return; }
String styleString = "-fx-background-color:" + toRGBCode(legendColor) + ";";
for(Node legendItem : legendItems){
Label legendLabel = (Label) legendItem;
Node legend = legendLabel.getGraphic(); //The legend icon (circle by default)
if(legend != null && legendLabel.getText().equals(itemToUpdate)){
legend.setStyle(styleString);
}
}
}
An example usage from what you've provided would be:
colorPicker.setOnAction(event -> {
Node node = chart.lookup(".default-color0.chart-pie");
String styleString = "-fx-background-color:" + toRGBCode(colorPicker.getValue()) + ";";
node.setStyle(styleString);
updateChartLegendColorFromItemName(chart, "Sunday", colorPicker.getValue());
});
Hopefully this helps
There is a private method updateLegend() in PieChart class that is called when chart data or their names are modified. This method creates new legend items for every PieChart.Data object and loads their styles from the data object’s styleClass:
for (Data item : getData()) {
LegendItem legenditem = new LegendItem(item.getName());
legenditem.getSymbol().getStyleClass().addAll(item.getNode().getStyleClass());
legenditem.getSymbol().getStyleClass().add("pie-legend-symbol");
legend.getItems().add(legenditem);
}
It means that calling setStyle() on chart’s data objects will have no effect on the legend color.
You could try to modify the legend objects directly but your change will probably be cancelled the next time updateLegend() is called unless you modify the style class of the pie’s node that you changed. Below is an example of how to change the color of the first pie and its legend item to the color that is already defined in your style sheet (e.g. the color of the second pie):
// reference to the first pie's node
Node node = chart.lookup(".default-color0.chart-pie");
// the default-color property is in the third line. It is defined by
// updateDataItemStyleClass(final Data item, int index) method in PieChart class
node.getStyleClass().set(2, "default-color1");
// we have to trigger some change so updateLegend() is called:
String name0 = pieChartData.get(0).getName();
pieChartData.get(0).setName("");
pieChartData.get(0).setName(name0);
To make this method work for your purpose you will need to update your CSS file (or create another one) in your ColorPicker handler. I don’t know if it is possible to create new style definitions without creation/modification of CSS files at runtime. The solution definitely does not look elegant but it looks like JavaFX was not designed to easily achieve what you need.
Related
I have TreeView filled by my own tree. In class Node I have field "type" which is one of NodeType. The problem is that I want have style for each type of NodeType, e.g. "type1" text color should be green, "type2" text color should be red. I'm new in javaFX. I found solution by james-d ( https://github.com/james-d/heterogeneous-tree-example ), but in this example css style depends on the class name, how can I make it for class field ?
View of TreeView
My understanding is you want a TreeCell that styles differently depending on the NodeType of the Node contained within the TreeItem of said TreeCell. All via CSS. Am I correct?
Assuming I am correct, there are 2 ways I can think of to accomplish this; both of which work best if there is a small number of known NodeTypes. The first involves the use of PseudoClass and the second uses the same strategy as the JavaFX Chart API.
First Option
Create a custom TreeCell that is tailored to using your Node type (i.e. specify the generic signature appropriately). In this custom TreeCell you declare as many PseudoClass static final fields as you need; one for each NodeType. Then you observe the NodeType of the whatever Node is currently displayed in the TreeCell and update the PseudoClass states accordingly.
Here is an example assuming NodeType is an enum that has two constants: HAPPY and SAD.
public class CustomTreeCell<T extends Node> extends TreeCell<T> {
private static final PseudoClass HAPPY = PseudoClass.getPseudoClass("happy");
private static final PseudoClass SAD = PseudoClass.getPseudoClass("sad");
// this listener will activate/deactivate the appropriate PseudoClass states
private final ChangeListener<NodeType> listener = (obs, oldVal, newVal) -> {
pseudoClassStateChanged(HAPPY, newVal == NodeType.HAPPY);
pseudoClassStateChanged(SAD, newVal == NodeType.SAD);
};
// use a weak listener to avoid a memory leak
private final WeakChangeListener<NodeType> weakListener = /* wrap listener */;
public CustomTreeCell() {
getStyleClass().add("custom-tree-cell");
itemProperty().addListener((obs, oldVal, newVal) -> {
if (oldVal != null) {
oldVal.nodeTypeProperty().removeListener(weakListener);
}
if (newVal != null) {
newVal.nodeTypeProperty().addListener(weakListener);
// need to "observe" the initial NodeType of the new Node item.
// You could call the listener manually to avoid code duplication
pseudoClassStateChanged(HAPPY, newVal.getNodeType() == NodeType.HAPPY);
pseudoClassStateChanged(SAD, newVal.getNodeType() == NodeType.SAD);
} else {
// no item in this cell so deactivate all PseudoClass's
pseudoClassStateChanged(HAPPY, false);
pseudoClassStateChanged(SAD, false);
}
});
}
}
Then in your CSS file you can use:
.custom-tree-cell:happy {
/* style when happy */
}
.custom-tree-cell:sad {
/* style when sad */
}
Second Option
Do what the JavaFX Chart API does when dealing with multiple series of data. What it does is dynamically update the style class of the nodes depending on the series' index in a list (e.g. .line-chart-series-data-<index> <-- probably not exactly this).
/*
* Create a custom TreeCell like in the first option but
* without any of the PseudoClass code. This listener should
* be added/removed from the Node item just like weakListener
* is above.
*/
ChangeListener<NodeType> listener = (obs, oldVal, newVal) -> {
// You have to make sure you keep "cell", "indexed-cell", and "tree-cell"
// in order to keep the basic modena styling.
if (newVal == NodeType.HAPPY) {
getStyleClass().setAll("cell", "indexed-cell", "tree-cell", "custom-tree-cell-happy");
} else if (newVal == NodeType.HAPPY) {
getStyleClass().setAll("cell", "indexed-cell", "tree-cell", "custom-tree-cell-sad");
} else {
getStyleClass().setAll("cell", "indexed-cell", "tree-cell"); // revert to regular TreeCell style
}
};
Then in CSS:
.custom-tree-cell-happy {
/* styles */
}
.custom-tree-cell-sad {
/* styles */
}
Both of these options are really only viable when there is a small set of known types. It might become unmaintainable when you have something like 10+ NodeTypes. It becomes pretty much impossible if the number of NodeTypes is dynamic at runtime.
It might be easier to have NodeType, or some intermediate class/data structure, know what color the text should be and set the color programmatically based on the NodeType.
Note: I quickly typed up the code in my answer and did not test it. There may be compiler errors, runtime exceptions, or logic errors in my code.
Edit
Something else came to mind. My code above assumes that NodeType is held in a property and can be changed during runtime. If NodeType is static (unchanging) for each Node then the code can be vastly simplified. Instead of using any listeners you can simple override the following method declared in javafx.scene.control.Cell:
protected void updateItem(Node item, boolean empty)
This method is called every time a new item is set on the cell. Read the documentation, however, as overriding this method requires certain things from the developer (such as calling the super implementation).
Im making buttons that need to switch between two colors every time they are pressed. I wanted to do that by comparing the style class to see if it matches either the "green" or "red" css class. Like so.
if(clickedBtn.getStyleClass() == "green") {
clickedBtn.getStyleClass().add("red");
} else {
clickedBtn.getStyleClass().add("green");
}
This doesn't work as it doesn't recognize "green" as anything. Is there a simpler way of doing this? I just need a graphic display with selectable seats. Thanks
.getStyleClass() retruns a ObservableList containing the style classes. This will never be the same object as a string literal, so the == check always yields false. The proper way of checking, if a node has a style class would be invoking the contains method of the list:
if (clickedBtn.getStyleClass().contains("green")) {
Since you probably want red and green to be mutual exclusive. you should also remove the style classes:
if(clickedBtn.getStyleClass().remove("green")) {
clickedBtn.getStyleClass().add("red");
} else {
clickedBtn.getStyleClass().remove("red");
clickedBtn.getStyleClass().add("green");
}
Using pseudo classes would probably be a bit more convenient however:
private final static PseudoClass GREEN = PseudoClass.getPseudoClass("green");
private final static PseudoClass RED = PseudoClass.getPseudoClass("red");
...
boolean isGreen = clickedBtn.getPseudoClassStates().contains(GREEN);
clickedBtn.pseudoClassStateChanged(GREEN, !isGreen);
clickedBtn.pseudoClassStateChanged(RED, isGreen);
I need to have an observable list of a type that will be displayed in a TableView with one single column, that when selected will display the rest of its information on the right. The TableView is wrapped in a TitledPane, which is wrapped in an Accordion. See image below:
As you can see in this scenario I don't want to show the Column Header.
I tried following the instruction here, which leads to here:
Pane header = (Pane) list.lookup("TableHeaderRow");
header.setMaxHeight(0);
header.setMinHeight(0);
header.setPrefHeight(0);
header.setVisible(false);
However, it appears to not be working for JavaFX 8. The lookup("TableHeaderRow") method returns null which makes me think that the "TableHeaderRow" selector no longer exist.
Is there an updated workaround for removing/hiding the table header in JavaFX 8?
I faced the problem of hiding column headers recently and could solve it using css.
I created a styleclass:
.noheader .column-header-background {
-fx-max-height: 0;
-fx-pref-height: 0;
-fx-min-height: 0;
}
and added it to the TableView:
tableView.getStyleClass().add("noheader");
Just in case someone needs an alternative approach. It also gives the flexibility of toggling column headers.
As observed in the comments, lookups do not work until after CSS has been applied to a node, which is typically on the first frame rendering that displays the node. Your suggested solution works fine as long as you execute the code you have posted after the table has been displayed.
For a better approach in this case, a single-column "table" without a header is just a ListView. The ListView has a cell rendering mechanism that is similar to that used for TableColumns (but is simpler as you don't have to worry about multiple columns). I would use a ListView in your scenario, instead of hacking the css to make the header disappear:
ListView<Album> albumList = new ListView<>();
albumList.setCellFactory((ListView<Album> lv) ->
new ListCell<Album>() {
#Override
public void updateItem(Album album, boolean empty) {
super.updateItem(album, empty);
if (empty) {
setText(null);
} else {
// use whatever data you need from the album
// object to get the correct displayed value:
setText(album.getTitle());
}
}
}
);
albumList.getSelectionModel().selectedItemProperty()
.addListener((ObservableValue<? extends Album> obs, Album oldAlbum, Album selectedAlbum) -> {
if (selectedAlbum != null) {
// do something with selectedAlbum
}
);
There's no need for CSS or style or skin manipulation. Simply make a subclass of TableView and override resize, like this
class XTableView extends TableView {
#Override
public void resize(double width, double height) {
super.resize(width, height);
Pane header = (Pane) lookup("TableHeaderRow");
header.setMinHeight(0);
header.setPrefHeight(0);
header.setMaxHeight(0);
header.setVisible(false);
}
}
This works fine as of June 2017 in Java 8.
Also, I would recommend using this nowadays.
tableView.skinProperty().addListener((a, b, newSkin) -> {
TableHeaderRow headerRow = ((TableViewSkinBase)
newSkin).getTableHeaderRow();
...
});
This can be executed during initialization, the other method as mention above, will return null, if run during initialization.
Combining the last two answers for a more generic solution without the need to override methods because getTableHeaderRow is no longer visible to be accessed. Tested with Java 11:
private void hideHeaders() {
table.skinProperty().addListener((a, b, newSkin) ->
{
Pane header = (Pane) table.lookup("TableHeaderRow");
header.setMinHeight(0);
header.setPrefHeight(0);
header.setMaxHeight(0);
header.setVisible(false);
});
}
I tried to change the BackgroundColor of specific TreeNodes during runtime inside my Class which contains my TreeViewer, but it doesn't work:
....
Display display = Display.getCurrent();
for (TreeItem item : treeItems) {
if (item.getParentItem() != null) {
Object parentElement = item.getParentItem().getData();
if(parentElement instanceof Or){
System.out.println(item);
Color color = new Color(display,12, 197, 77);
item.setBackground(color);
item.setForeground(color);
}
}
}
......
I'm wondering why this doesn't work, as TreeItem has the according methods for this.
Note that I don't want to use my Labelprovider for this, as I've to check several dependencies between nodes to determine the right Color and therefor, the Labelprovider don't fit in.
Cheers,
Phil
Check this example here. For more examples on JFace see here.
I was used to Swing and I'm now beginning with FX.
I came across a question which I couldn't find a answer reading the "Working With Layouts in JavaFX " Guide from Oracle and also doing some research on the internet.
In the FX API Guide for the GridPane Class there is an example about laying out the Objects:
here an excerpt from http://docs.oracle.com/javafx/2/api/javafx/scene/layout/GridPane.html :
GridPane gridpane = new GridPane();
// Set one constraint at a time...
Button button = new Button();
GridPane.setRowIndex(button, 1);
GridPane.setColumnIndex(button, 2);
...
// don't forget to add children to gridpane
gridpane.getChildren().addAll(button, label);
The row and column information is set via static methods of GridPane. This is also what the documentation says. I'd like to understand where this Layout Constraints are bound to the Node Object - in this case the button.
The Node API doc does not mention the layout constraints.
I found a lot information about setting Constraints eg for columns on a GridPane object, but I could not find about further information about this.
So how is the row/column information bound to the button or how can I retrieve this information from the button after it was applied ?
best regrads
guenter
Reading through the javaFX source code, GridPane's setRowIndex and setColumnIndex use the setConstraint method of it's superclass Pane, which looks like this:
static void setConstraint(Node node, Object key, Object value) {
if (value == null) {
node.getProperties().remove(key);
} else {
node.getProperties().put(key, value);
}
if (node.getParent() != null) {
node.getParent().requestLayout();
}
}
So the information gets stored directly in the node.