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.
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).
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.
I'm trying to build a dynamic web app in GWT, when widgets are added to the screen I remember their 'name' by setting the Widget.setId, when I want to replace part of the page, I can find the element in question via DOM.getElementById('name'), and its parent using DOM.getParentElement(), and then remove its children.
Now I have a com.google.gwt.dom.client.Element object (the parent). What I want to do is turn this back into a GWT object - in fact it'll be something derived from Panel, so I can add additional Widgets.
How do I go from the Element object back to a Panel object ?
I totally accept I could be going about this the wrong way, in which case is there a better way?
I think your approach to remove widgets from the DOM using DOM.getElementById('name') is not the proper one.
On your case (I am just figuring out what you do), I would keep Java Objects references instead of accessing to them using the DOM.
For instance:
HorizontalPanel panel = new HorizontalPanel();
Widget w = new Widget();
//We add the one widget to the panel
panel.add(w);
//One more widget added
w = new Widget();
panel.add(w);
//Now we remove all the widgets from the panel
for(int i = 0; i < panel.getWidgetCount(); i++){
panel.remove(panel.getWidget(i));
}
UPDATE
Based on your comments, I would propose the following solution.
I suppose that you are storing widgets on HorizontalPanel, just apply this solution to your concrete case.
I propose to use customized class which inherits from HorizontalPanel and add a Map there to store relationship between names and widgets.
public class MyHorizontalPanel extends HorizontalPanel {
private Map<String, Widget> widgetsMap;
public MyHorizontalPanel(){
super();
widgetsMap = new HashMap<String, Widget>();
}
//We use Map to store the relationship between widget and name
public void aadWidget(Widget w, String name){
this.add(w);
widgetsMap.put(name, w);
}
//When we want to delete and just have the name, we can search the key on the map.
//It is important to remove all references to the widget (panel and map)
public void removeWidget(String name){
this.remove(widgetsMap.get(name));
widgetsMap.remove(name);
}
}
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);
});
}
Unfortunatly I am unable to provide code to this site due to where I work. With that said I will be as detailed as I can. I am working on using aan XML file to populate a JcomboBox based of the "Name" element. I have that part working. The way I am doing this is by using DOM method and I create in Object for each of the Nodes and then I uset set methods to grab the attributes that I require.
Where I am now is I need to populate a text field based off of what was selected. I am struggling to figure out how to associate what is selected to what I need. For instance let say I have a node called "Reference_Point_ID" and I needed to pull the child node called "Latitude" to populate the JTextField. How would I associate the child node with the parent node to pull the correct data?
Again I am sorry I cannout provide the code but any help will be much appreciated. Thank you.
UPDATE - SOLUTION
For anyone else that may need this info.
In order to pull the data for what I needed into the JComboBox I had to modify the model like so:
public TestReferencePointXMLReaderGUI()
{
initComponents();
ReferencePointReader referencePointReader = new ReferencePointReader("path to your xml file");
List<ReferencePointObject> listOfData = referencePointReader.getData();
DefaultComboBoxModel<ReferencePointObject> model =
(DefaultComboBoxModel<ReferencePointObject>) jComboBoxRefPointSelector.getModel();
for (ReferencePointObject referencePointObject : listOfData)
{
model.addElement(referencePointObject);
}
}
The following shows how I changed the data in the text fields based off of what was selected. There is something I would like to mention. Unless you want the ItemStateChanged to return the previous selection as well as the new selection you need to be sure to add the if check statement.
private void jComboBoxRefPointSelectorItemStateChanged(java.awt.event.ItemEvent evt)
{
if (evt.getStateChange() == ItemEvent.SELECTED)
{
Object selected = jComboBoxRefPointSelector.getSelectedItem();
ReferencePointObject selectedReferencePoint = (ReferencePointObject) selected;
jTextFieldLat.setText(selectedReferencePoint.getLat());
jTextFieldLong.setText(selectedReferencePoint.getLng());
}
}