Javafx Style by Classname

How do I get an HTML Element By CSS class name via a JavaFX WebEngine?

I came across this and saw there wasn't much for answers. The way I found way to work with dom classes is not great but it gets the job done.

To add a class on a Node you obtained, use the setAttribute() method. Be careful to maintain any classes that might already exist on the Node.

Document doc = engine.getDocument();
if (doc != null){
Element elem = doc.getElementById('someID');
String classNames = elem.getAttribute("class");
classNames += " someClass"; //note the whitespace!
elem.setAttribute("class", classNames);
}

Then, if you wish to search the DOM by class you can execute javascript to do so using the executeScript on the WebEngine. The return type depends on what you're asking the script to do.

Small note: disabling javascript via engine.setJavaScriptEnabled(false); does not prohibit use of the engine.executeScript() method.

HTMLCollection result =  (HTMLCollection)engine.executeScript("document.getElementsByClassName('someClass')");

However inefficient, I did this to determine what I would be getting back from executeScript() before writing any further code:

Object result =  engine.executeScript("document.getElementsByClassName('someClass')");
System.out.println(result.getClass().getName());

Like I said it isn't great, but you can write some wrapper functions to make it easier to work with. Hope this helps!

JavaFX CSS style inheritance

A quick and simple answer is Slaw's comment (which may not use CSS inheritance at all):

.base_arc, .child_arc { /* common styles */ } 
.child_arc { /* specific styles */ }

A longer answer, discussing CSS inheritance and its relationship with object-oriented inheritance is below.

Background on CSS inheritance vs object-oriented inheritance

CSS inheritance is not like the object oriented inheritance that Java has.

Instead, CSS inheritance is based upon the node position in the scene graph. Child nodes can inherit CSS properties from their parents (if the CSS property is inheritable). Inheritance is based upon position in the scene graph, not on the Java class type hierarchy. This is documented in the JavaFX CSS documentation on inheritance.

Applying CSS inheritance to your example

Create CSS rules for the parent node

Let's say you have a Pane in which you draw your arcs, and you set the style drawing-pane on it. If you have the following css rule:

.drawing-pane Arc { 
-fx-stroke: black;
}

, then all arcs drawn in the pane would be black. I didn't test that, but it is my understanding of how it works.

The Arc rule is a CSS type selector, so .drawing-pane Arc will select any arcs which have been drawn in the drawing pane.

Create CSS rules for specific types of child nodes

Now, to differentiate different arcs to have different styles, you need to have an additional, more specific, CSS rule which applies the specific style.

So, if you create the following rule:

.drawing-pane .child-arc {
-fx-stroke-line-cap: BUTT;
}

, then all of the arcs which are added to the drawing pane which also have the style class child-arc assigned to then will get a butt cap. They will also have a black stroke as the previous drawing-pane Arc rule still also applies to them (through CSS inheritance).

Associate your nodes with appropriate CSS rules

There are various ways you could associate the child-arc class with an arc, for example with a factory method:

Arc createChildArc() {
Arc arc = new Arc();
arc.getStyleCass().add("child-arc");
}

Or via inheritance by setting the style in a constructor:

public class ChildArc extends Arc {
public ChildArc() {
getStyleClass().add("child-arc");
}
}

Using CSS type selectors rather than class selectors

Note: it is possible to use a type selector (no . prefix and refers to a simple, non-package prefixed Java class name) rather than a css class selector (uses a . prefix), so you could do:

public class ChildArc extends Arc {}

and have CSS as:

.drawing-pane ChildArc {
-fx-stroke-line-cap: BUTT;
}

But, in general, the css style classes are probably a bit more flexible and also in more common usage then the type selectors, so I'd probably just stick with the class selectors.


I'm not really sure if this is the answer you really wanted, but it is my understanding of one way to solve the problem you currently have.

I think what you are really looking for is the info on SASS outlined below, though, in general it isn't how the problem would be solved when using straight CSS without additional tooling.

Using SASS to add object-oriented inheritance to CSS

If you use a pre-processor such as SASS on your css style sheets, you can bring a lot more features (from SASS) into your style sheets. The features that SASS supports include mix-ins and extensions for CSS styles. So SASS makes CSS more object oriented in how it defines its styling rules, by allowing object-oriented style inheritance of style information.

Whether you want to invest the time and assume the complexity to learn SASS and implement it into your build chain is up to you. Personally, for myself, I wouldn't use SASS unless I were writing an awful lot of CSS, which I just don't do.

The standard default css for JavaFX (modena.css), is large, complex and feature rich, and does not make use of SASS style features in its implementation. Studying modena.css is the best way to learn JavaFX CSS best usage practices and principles. If SASS isn't required for something as complicated as modena.css, then it is unlikely to be really necessary for the CSS stylesheets you create for your application.

JavaFX css class style

Add these lines to your css file

.diagram-pane {
-fx-background-color: #f8ecc2;
-fx-font-size: 8pt;
}

and set DiagramPane instance to use diagram-pane style class

diagramPane.getStyleClass().clear();
diagramPane.getStyleClass().add("diagram-pane");

Is possible to add css class for a Node object in javaFx?

Use a CSS Pseudoclass.

Give all the nodes you are going to drag some fixed style class:

private static final String DRAGGABLE_CLASS = "draggable" ;

// ...

Node node = ... ;
node.getStyleClass().add(DRAGGABLE_CLASS);

Now define a "selected" pseudoclass:

private static final PseudoClass SELECTED_PSEUDO_CLASS =
PseudoClass.getPseudoClass("selected");

And then do:

public void addSelectionControlToNode(Node node) {
node.addEventFilter(MouseEvent.MOUSE_CLICKED, (MouseEvent e) -> {
if (e.isControlDown()) {
if (selection.contains(node)) {
selection.remove(node);
node.pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, false);
} else {
selection.add(node);
node.pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, true);
}
} else {
selection.clear();
selection.add(node);
node.pseudoClassStateChanged(SELECTED_PSEUDO_CLASS, true);
}
System.out.println(selection.size());
});
}

Now you can define an external CSS file with:

.draggable {
/* styles for all draggable nodes */
}
.draggable:selected {
-fx-border-width: 2;
-fx-border-color: gray;
-fx-border-style: solid;
}

JavaFX 2.2 built-in controls have no default style

In JavaFX, three CSS selectors are supported, which are analogous to Type Selectors, Class selectors, and ID selectors in standard CSS. It's important to be a bit careful with the word "class", which has different meanings in the Java world and the CSS world: a Java class is a CSS Type; a CSS class is set on a node via a property called styleClass.

The Type Selector is determined by the result of calling the method getTypeSelector, which is declared in the interface Styleable. Node implements this interface, and implements getTypeSelector to return

getClass().getName() without the package name

So any Node may be styled by specifying its simple class name:

Label {
-fx-text-fill: green ;
}
Line {
-fx-stroke: red ;
}

etc.

CSS classes are determined by the getStyleClass() method in Styleable, which returns a list of strings. (Note that nodes may have multiple style classes, though I have never understood why this is a List rather than a Set.) By default, this list is empty, but Control subclasses set this value via their default skin to be the "css-ized" version of the class name (Button becomes button, ComboBox becomes combo-box, etc.). Thus

.label {
-fx-text-fill: green ;
}

works, but

.line {
-fx-stroke: red ;
}

does not work without previously setting the style class:

Line line = new Line();
line.getStyleClass().add("line");

Finally, ID Selectors work by specifying an id on the node. Ids are intended to be unique among all nodes in the scene graph. So you can do

Line line = new Line();
line.setId("my-line");

with

#my-line {
-fx-stroke: red ;
}

So in your example, the style class is not set by default (Line is not a Control subclass). You must either set it "by hand", or use a type selector.

One last note: you mentioned that your line "lacked caspian.css". I'm not quite sure what that means (as Lines are not styled at all by the default stylesheet), but the default stylesheet is only loaded the first time a Control is instantiated. This is intended as a performance enhancement for scene graphs that use no controls (and so would not benefit from the default stylesheet anyway).

JavaFX TreeItem css style for different class field

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).



Related Topics



Leave a reply



Submit