I am learning JavaFx and building a sample app that would display a list of restaurant menu items in a scroll-able way. I figured out the best way to do this is using GridView control from controlsfx because even with the large set of menu items the scroll would work fast. Here is a sample code that I am trying to make it work:
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
VBox root = new VBox();
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
ObservableList<GridPane> list = FXCollections.<GridPane>observableArrayList();
GridView<GridPane> gridView = new GridView<>(list);
gridView.setCellFactory(new Callback<GridView<GridPane>, GridCell<GridPane>>() {
public GridCell<GridPane> call(GridView<GridPane> gridView) {
return new GridCell<GridPane>();
}
});
Label lblName1 = new Label("Name");
GridPane grid = new GridPane();
grid.addRow(0, lblName1);
Label lblName2 = new Label("Price");
grid.addRow(1, lblName2);
list.add(grid);
root.getChildren().add(gridView);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
When I run the code above it only shows the VBox with no item. I have tried to replicate the sample code given on controlsfx website on using GridView (http://controlsfx.bitbucket.org/org/controlsfx/control/GridView.html).
Any help/tip would be much appreciated. Thanks.
Figured out the problem and here is the correct code. I needed to write the custom GridPane (for dsplaying menu items) and the cell factory for generating the custom object.
//Main Class
package application;
import java.util.Random;
import org.controlsfx.control.GridCell;
import org.controlsfx.control.GridView;
import org.controlsfx.control.cell.ColorGridCell;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.util.Callback;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
try {
VBox root = new VBox();
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
GridView<MenuItem> menuItems = new GridView<>();
for(int i = 0; i < 10000; i++) {
menuItems.getItems().addAll(new MenuItem(i));
}
menuItems.setCellFactory(new MenuItemCellFactory());
root.getChildren().add(menuItems);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
/**
* Custom Menu Item
*/
package application;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
/**
* #author Rahul
*
*/
public class MenuItem extends GridPane {
private Integer name = null;
public MenuItem(int i) {
// TODO Auto-generated constructor stub
this.name = i;
}
public Integer getName() {
return name;
}
public void setName(Integer name) {
this.name = name;
}
}
//Menu Item Cell Factory
package application;
import org.controlsfx.control.GridCell;
import org.controlsfx.control.GridView;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.GridPane;
import javafx.util.Callback;
public class MenuItemCellFactory implements Callback<GridView<MenuItem>, GridCell<MenuItem>> {
#Override
public GridCell<MenuItem> call(GridView<MenuItem> listview) {
return new MenuItemCell();
}
}
//Menu Item Cell
package application;
import org.controlsfx.control.GridCell;
import javafx.scene.control.ListCell;
import javafx.scene.layout.GridPane;
public class MenuItemCell extends GridCell<MenuItem> {
#Override
protected void updateItem(MenuItem item, boolean empty) {
// TODO Auto-generated method stub
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
setText(item.getName().toString());
setStyle("-fx-background-color: #ffffff; -fx-background-radius: 15; -fx-border-radius: 15; -fx-border-width: 0; -fx-padding: 10; -fx-pref-width: 145; -fx-max-width: 145; -fx-max-width: 145; -fx-pref-height: 130; -fx-max-height: 130; -fx-effect: dropshadow(three-pass-box, #93948d, 10, 0, 0, 0);");
}
}
}
This is a sample code to use GridView containing list of GridPane using cellfactory. Please modify it as per your requirement. The basic would remain the same.
Suggestions and feedback are most welcome. Thanks.
Related
I try to create a TreeView that will have on every branch and leaf a ComboBox, an "add child" Button and an "remove child" Button.
Here is my code
package example;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
*
* #author me
*/
public class Example extends Application {
String[] companyNames = new String[10];
#Override
public void start(Stage primaryStage) {
companyNames[0]="FORD";
companyNames[1]="MG";
companyNames[2]="LADA";
companyNames[3]="BMW";
companyNames[4]="KIA";
companyNames[5]="HYUNDAI";
companyNames[6]="AUDI";
companyNames[7]="MERCEDES";
companyNames[8]="DACIA";
companyNames[9]="TOYOTA";
final TreeItem<String> treeRootItem = new TreeItem<>("Root");
final TreeView<String> treeView = new TreeView<>(treeRootItem);
treeRootItem.setExpanded(true);
treeView.setCellFactory(e -> new CustomCell());
VBox mainVBox = new VBox();
mainVBox.getChildren().add(treeView);
Scene scene1 = new Scene(mainVBox,1600,600);
primaryStage.setScene(scene1);
primaryStage.show();
primaryStage.centerOnScreen();
}
//function custom TreeCell CustomCell
class CustomCell extends TreeCell<String> {
#Override
protected void updateItem(String item, boolean empty)
{
ComboBox companiesComboBox = new ComboBox();
Button addChildButton = new Button("Add Child");
Button removeButton = new Button("Remove Child");
super.updateItem(item, empty);
if (isEmpty()) {
setGraphic(null);
setText(null);
}
else {
HBox cellBox = new HBox(10);
if (item.equals("Root"))
{
cellBox.getChildren().addAll(companiesComboBox,addChildButton);
}
else cellBox.getChildren().addAll(companiesComboBox,addChildButton,removeButton);
setGraphic(cellBox);
setText(null);
}
companiesComboBox.getItems().addAll((Object[]) companyNames);
addChildButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
String selection=companiesComboBox.getSelectionModel().getSelectedItem().toString();
TreeItem<String> childNode1 = new TreeItem<>("Child Node");
getTreeItem().getChildren().add(childNode1);
}});
removeButton.setOnAction(value -> {
//somehow i will remove the leaf/branch
});
}
}
public static void main(String[] args) {
launch(args);
}
}
(As Alexandra pointed out, in my prior post, i had many Java Naming Conventions wrong, i hope i fixed it now.
A am sorry that i deleted my prior post but it was off topic with many wrongs so i decided to make a small research how to make a post with better code and more specific without mistakes and come back a few days later.I hope that now is presentable.)
The problem that i have is that when i add a new child , the hole tree loses the ComboBox selection.
Here is a picture of the project running https://ibb.co/8MVDqhX
Any help/suggestion will be appreciated
UPDATE:
After Slaw answer i tried to create a Model Class named SelectedCompanyClass
package example;
import javafx.beans.property.StringProperty;
/**
*
* #author me
*/
public class SelectedCompanyClass {
public String companyNameString;
public SelectedCompanyClass(String companyNameString) {
this.companyNameString = companyNameString;
}
}
And i changed my main class like this
package example;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
*
* #author me
*/
public class Example extends Application {
String[] companyNames = new String[10];
int x=0;
SelectedCompanyClass SelectedCompanyClass1;
#Override
public void start(Stage primaryStage) {
this.SelectedCompanyClass1 = new SelectedCompanyClass("root");
companyNames[0]="FORD";
companyNames[1]="MG";
companyNames[2]="LADA";
companyNames[3]="BMW";
companyNames[4]="KIA";
companyNames[5]="HYUNDAI";
companyNames[6]="AUDI";
companyNames[7]="MERCEDES";
companyNames[8]="DACIA";
companyNames[9]="TOYOTA";
final TreeItem<SelectedCompanyClass> treeRootItem = new TreeItem<>(SelectedCompanyClass1);
final TreeView<SelectedCompanyClass> treeView = new TreeView<>(treeRootItem);
treeRootItem.setExpanded(true);
treeView.setCellFactory(e -> new CustomCell());
VBox mainVBox = new VBox();
mainVBox.getChildren().add(treeView);
Scene scene1 = new Scene(mainVBox,1600,600);
primaryStage.setScene(scene1);
primaryStage.show();
primaryStage.centerOnScreen();
System.out.println("Start runs");
}
class CustomCell extends TreeCell<SelectedCompanyClass> {
protected void updateItem(SelectedCompanyClass SelectedCompanyClass1, boolean empty)
{
ComboBox companiesComboBox = new ComboBox();
Button addChildButton = new Button("Add Child");
Button removeButton = new Button("Remove Child");
super.updateItem(SelectedCompanyClass1,empty);
if (isEmpty()) {
setGraphic(null);
setText(null);
}
else {
HBox cellHBox = new HBox();
if (SelectedCompanyClass1!=null)
{
if (SelectedCompanyClass1.companyNameString.equals("Root"))
{
cellHBox.getChildren().addAll(companiesComboBox,addChildButton);
}
else cellHBox.getChildren().addAll(companiesComboBox,addChildButton,removeButton);
companiesComboBox.getSelectionModel().select(SelectedCompanyClass1.companyNameString);
}
setGraphic(cellHBox);
setText(null);
}
companiesComboBox.getItems().addAll((Object[]) companyNames);
addChildButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
String selection="none";
SelectedCompanyClass SelectedCompanyClass2 = new SelectedCompanyClass(selection);
if (companiesComboBox.getSelectionModel().getSelectedItem().toString()!=null) {
selection=companiesComboBox.getSelectionModel().getSelectedItem().toString();
SelectedCompanyClass2.companyNameString=selection;
}
TreeItem<SelectedCompanyClass> childNode2 = new TreeItem<>(SelectedCompanyClass2);
getTreeItem().getChildren().add(childNode2);
childNode2.setExpanded(true);
}});
removeButton.setOnAction(value -> {
//somehow i will remove the leaf/branch
});
}
}
public static void main(String[] args) {
launch(args);
}
}
Now, thanks to Slaw , i have made progress and i create new objects TreeItem as childs, but parent Node loses its ComboBox selection
UPDATE 2:
After the usefull suggestions, i finally done it, here is the code
The model Class code
package example;
import javafx.beans.property.StringProperty;
public class SelectedCompanyClass {
String companyNameString = new String();
public SelectedCompanyClass() {
companyNameString="Choose";
}
}
and the Main code
package example;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
*
* #author me
*/
public class Example extends Application {
String[] companyNames = new String[10];
int x=0;
#Override
public void start(Stage primaryStage) {
SelectedCompanyClass SelectedCompanyClass1 = new SelectedCompanyClass();
companyNames[0]="FORD";
companyNames[1]="MG";
companyNames[2]="LADA";
companyNames[3]="BMW";
companyNames[4]="KIA";
companyNames[5]="HYUNDAI";
companyNames[6]="AUDI";
companyNames[7]="MERCEDES";
companyNames[8]="DACIA";
companyNames[9]="TOYOTA";
final TreeItem<SelectedCompanyClass> treeRootItem = new TreeItem<>(SelectedCompanyClass1);
final TreeView<SelectedCompanyClass> treeView = new TreeView<>(treeRootItem);
treeRootItem.setExpanded(true);
treeView.setCellFactory(e -> new CustomCell());
VBox mainVBox = new VBox();
mainVBox.getChildren().add(treeView);
Scene scene1 = new Scene(mainVBox,1600,600);
primaryStage.setScene(scene1);
primaryStage.show();
primaryStage.centerOnScreen();
System.out.println("Start runs");
}
class CustomCell extends TreeCell<SelectedCompanyClass> {
protected void updateItem(SelectedCompanyClass SelectedCompanyClass1, boolean empty)
{
ComboBox companiesComboBox = new ComboBox();
Button addChildButton = new Button("Add Child");
Button removeButton = new Button("Remove");
Label objectLabel = new Label();
companiesComboBox.valueProperty().addListener((obs, oldValue, newValue) -> {
SelectedCompanyClass1.companyNameString=newValue.toString();
});
super.updateItem(SelectedCompanyClass1,empty);
if (isEmpty()) {
setGraphic(null);
setText(null);
}
else {
HBox cellHBox = new HBox();
if (SelectedCompanyClass1!=null)
{
cellHBox.getChildren().addAll(companiesComboBox,addChildButton,removeButton,objectLabel);
companiesComboBox.getSelectionModel().select(SelectedCompanyClass1.companyNameString);
}
setGraphic(cellHBox);
setText(null);
}
companiesComboBox.getItems().addAll((Object[]) companyNames);
addChildButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
String selection="none";
SelectedCompanyClass SelectedCompanyClass2 = new SelectedCompanyClass();
TreeItem<SelectedCompanyClass> childNode2 = new TreeItem<>(SelectedCompanyClass2);
getTreeItem().getChildren().add(childNode2);
childNode2.setExpanded(true);
companiesComboBox.valueProperty().addListener((obs, oldValue, newValue) -> {
SelectedCompanyClass2.companyNameString=newValue.toString();
});
}});
removeButton.setOnAction(value -> {
//somehow i will remove the leaf/branch
});
}
}
public static void main(String[] args) {
launch(args);
}
}
Best regards to all
I´m trying to implement a very basic, simple and quiet typical dialog showing a list of checkboxes and a button for selecting all these checkboxes of the list.
The actual selection is working as expected. I also change the background color of the actual box showing the check symbol. But this color is only applied to the checkboxes visible without scrolling the list. When scrolling down the list you can see that all checkboxes are selected but the color is not set.
I´m using JDK10.
How can I force applying the color?
EDIT:
When first scrolling down and then clicking "select all" all colors of the previous checkboxes are also set correctly. But still not the following ones.
EDIT:
The same effect occurs when filtering the boxes using instanceof and change the colors by Region#setBackground(...).
package javafxcssbroken;
import java.util.StringJoiner;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class JavaFXCssBroken extends Application {
#Override
public void start(Stage primaryStage) {
ListView<CheckBox> listView = new ListView();
for (int i = 0; i < 100; i++) {
listView.getItems().add(new CheckBox("Element " + i));
}
Button markAll = new Button("Select all");
markAll.setOnAction(aevt -> {
Color newColor = Color.BLUE;
listView.getItems().stream()
.forEach(checkbox -> {
checkbox.setSelected(true);
checkbox.getChildrenUnmodifiable().stream()
.forEach(child -> child.setStyle(new StringJoiner(", ", "-fx-background-color: rgba(", ")")
.add(Double.toString(255 * newColor.getRed()))
.add(Double.toString(255 * newColor.getGreen()))
.add(Double.toString(255 * newColor.getBlue()))
.add(Double.toString(newColor.getOpacity()))
.toString()));
});
});
VBox vbox = new VBox(listView, markAll);
StackPane root = new StackPane();
root.getChildren().add(vbox);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("JavaFXCssBroken");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
EDIT:
So a solution should be to introduce a class representing the data itself and also its associations discussed in the comments.
JavaFXCssBroken.java
package javafxcssbroken;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javax.swing.text.StyleConstants;
/**
* #param <I> The type of the items to display.
* #param <G> The type of the groups to associate items with.
*/
public class JavaFXCssBroken<I, G> extends Application {
private ListView<AssociatedItem<I, G>> listView;
private Set<I> items;
private Map<G, Color> groups;
private Optional<G> currentGroup;
public JavaFXCssBroken(Set<I> items, Map<G, Color> groups, Optional<G> initialGroup){
this.items = items;
this.groups = groups;
this.currentGroup = initialGroup;
}
#Override
public void start(Stage primaryStage) {
listView = new ListView();
listView.setCellFactory(lv -> {
ListCell<AssociatedItem<I, G>> cell = new ListCell<>();
cell.itemProperty().addListener((obs, oldVal, newVal) -> {
if(!cell.isEmpty() && newVal != null && newVal.getGroup().isPresent()){
cell.setBackground(new Background(
new BackgroundFill(groups.get(newVal.getGroup().get()), CornerRadii.EMPTY, Insets.EMPTY)));
}
});
cell.emptyProperty().addListener((obs, oldVal, newVal) -> {
if(newVal){
cell.setBackground(Background.EMPTY);
}
});
return cell;
});
items.stream().forEach(item -> listView.getItems().add(new AssociatedItem(item)));
Button markAll = new Button("Select all");
markAll.setOnAction(aevt -> {
listView.getItems().stream()
.forEach(item -> item.setGroup(currentGroup));
});
VBox vbox = new VBox(listView, markAll);
StackPane root = new StackPane();
root.getChildren().add(vbox);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("JavaFXCssBroken");
primaryStage.setScene(scene);
primaryStage.show();
}
}
AssociatedItem.java
package javafxcssbroken;
import java.util.Optional;
public class AssociatedItem<I, G> {
private I item;
private Optional<G> group;
public AssociatedItem(I item) {
this.item = item;
group = Optional.empty();
}
public I getItem() {
return item;
}
public void setItem(I item) {
this.item = item;
}
public Optional<G> getGroup() {
return group;
}
public void setGroup(Optional<G> group) {
this.group = group;
}
#Override
public String toString() {
return item.toString();
}
}
Main.java
package javafxcssbroken;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javafx.application.Application;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class Main extends Application{
#Override
public void start(Stage primaryStage) {
Map<Integer, Color> groups = Map.of(0, Color.RED, 1, Color.BLUE);
Set<String> items = new HashSet<>();
for (int i = 0; i < 100; i++) {
items.add("Elements " + i);
}
new JavaFXCssBroken(items, groups, Optional.of(0)).start(primaryStage);
}
public static void main(String[] args) {
launch(args);
}
}
At this point colors are set to all elements but the update of the colors is only applied when starting to scroll. Is it enough to introduce AssociatedItems as Observable?
Kleopatra is right. But assuming it is a simple example or training (even if you should take the good practice as soon as possible i.e even during the training), just use the css strength, create a stylesheet with this content :
StyleCheckbox.css
.check-box:selected > .box {
-fx-background-color:blue;
}
And modify your exisiting code by commenting the useless mechanic, and by adding the stylesheet you just create. :
markAll.setOnAction(aevt -> {
// Color newColor = Color.BLUE;
listView.getItems().stream().forEach(checkbox -> {
checkbox.setSelected(true);
// checkbox.getChildrenUnmodifiable().stream()
// .forEach(child -> child.setStyle(new StringJoiner(", ", "-fx-background-color:
// rgba(", ")")
// .add(Double.toString(255 * newColor.getRed()))
// .add(Double.toString(255 * newColor.getGreen()))
// .add(Double.toString(255 *
// newColor.getBlue())).add(Double.toString(newColor.getOpacity()))
// .toString()));
});
});
VBox vbox = new VBox(listView, markAll);
vbox.getStylesheets().add(this.getClass().getResource("StyleCheckbox.css").toExternalForm());
As kleopatra suggested overriding updateItem(...) and using an extractor works.
Main.java remains the same as in the last edit of the question.
AssociatedItem.java
package javafxcssbroken;
import java.util.Optional;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
public class AssociatedItem<I, G> {
private final ObjectProperty<I> item = new SimpleObjectProperty<>();
private final ObjectProperty<Optional<G>> group = new SimpleObjectProperty<>();
public AssociatedItem(I item) {
this.item.set(item);
group.set(Optional.empty());
}
public ObjectProperty<I> itemProperty() {
return item;
}
public I getItem() {
return itemProperty().get();
}
public void setItem(I item) {
this.item.set(item);
}
public ObjectProperty<Optional<G>> groupProperty() {
return group;
}
public Optional<G> getGroup() {
return groupProperty().get();
}
public void setGroup(Optional<G> group) {
this.group.set(group);
}
}
JavaFXCssBroken.java
package javafxcssbroken;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableObjectValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
* #param <I> The type of the items to display.
* #param <G> The type of the groups to associate items with.
*/
public class JavaFXCssBroken<I, G> extends Application {
private ListView<AssociatedItem<I, G>> listView;
private Set<I> items;
private ObservableValue<ObservableList<AssociatedItem<I, G>>> associatedItems
= new SimpleObjectProperty<>(
FXCollections.observableArrayList(i -> new Observable[]{i.itemProperty(), i.groupProperty()}));
private Map<G, Color> groups;
private Optional<G> currentGroup;
public JavaFXCssBroken(Set<I> items, Map<G, Color> groups, Optional<G> initialGroup) {
this.items = items;
this.groups = groups;
this.currentGroup = initialGroup;
}
#Override
public void start(Stage primaryStage) {
listView = new ListView();
listView.setCellFactory(lv -> new ListCell<AssociatedItem<I, G>>() {
#Override
protected void updateItem(AssociatedItem<I, G> item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setBackground(Background.EMPTY);
} else {
item.getGroup().ifPresent(group -> {
setBackground(new Background(
new BackgroundFill(groups.get(group), CornerRadii.EMPTY, Insets.EMPTY)));
});
setText(item.getItem().toString());
}
}
});
listView.itemsProperty().bind(associatedItems);
items.stream().forEach(item -> {
associatedItems.getValue().add(new AssociatedItem<>(item));
});
Button markAll = new Button("Select all");
markAll.setOnAction(aevt -> {
listView.getItems().stream()
.forEach(item -> item.setGroup(currentGroup));
});
VBox vbox = new VBox(listView, markAll);
StackPane root = new StackPane();
root.getChildren().add(vbox);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("JavaFXCssBroken");
primaryStage.setScene(scene);
primaryStage.show();
}
}
I'm working with RichTextFx's CodeArea to highlight custom mini language code.
Now while executing this code I want to show a small arrow in front of current executed line. I know the specific line number but can't get anything to happen with the line number label.
Since github project claims showing line numbers or breakpoint toggles as a feature this can't be very difficult. But can't get anything to work...
Thanks in advance
To show any graphic in front of the line, you need to set the "paragraph graphic factory" of the CodeArea. This graphic factory is just a function int -> Node: given the line number, it returns a Node that will be displayed in front of the line.
Here is a graphic factory that produces a green triangle pointing at the line. It will only be shown when the line is equal to the given integer property shownLine.
class ArrowFactory implements IntFunction<Node> {
private final ObservableValue<Integer> shownLine;
ArrowFactory(ObservableValue<Integer> shownLine) {
this.shownLine = shownLine;
}
#Override
public Node apply(int lineNumber) {
Polygon triangle = new Polygon(0.0, 0.0, 10.0, 5.0, 0.0, 10.0);
triangle.setFill(Color.GREEN);
ObservableValue<Boolean> visible = Val.map(
shownLine,
sl -> sl == lineNumber);
triangle.visibleProperty().bind(visible.conditionOnShowing(triangle));
return triangle;
}
}
Each graphic (i.e. little green triangle) you create will be observing the given shownLine property to decide whether it should be visible. As lines, and therefore line graphics, come and go, it is important to remove the listener of shownLine when the graphic is no longer used. visible.conditionOnShowing(triangle) is a new property that will stop observing the visible property (and automatically also the shownLine property, thanks to ReactFX's lazy binding semantics) and reset to constant false whenever the triangle is not part of a showing window. So we don't cause memory or CPU leaks due to uncleaned listeners.
Here is a complete runnable demo that uses this ArrowFactory combined with the LineNumberFactory provided by RichTextFX to show both line numbers and a little triangle. This demo uses the CodeArea's current line as the shownLine property. You will want to substitute it for a property that contains the current line of execution.
import java.util.function.IntFunction;
import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.stage.Stage;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory;
import org.reactfx.value.Val;
public class CodeAreaWithLineIndicator extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
CodeArea codeArea = new CodeArea();
IntFunction<Node> numberFactory = LineNumberFactory.get(codeArea);
IntFunction<Node> arrowFactory = new ArrowFactory(codeArea.currentParagraphProperty());
IntFunction<Node> graphicFactory = line -> {
HBox hbox = new HBox(
numberFactory.apply(line),
arrowFactory.apply(line));
hbox.setAlignment(Pos.CENTER_LEFT);
return hbox;
};
codeArea.setParagraphGraphicFactory(graphicFactory);
primaryStage.setScene(new Scene(new StackPane(codeArea), 600, 400));
primaryStage.show();
}
}
class ArrowFactory implements IntFunction<Node> {
private final ObservableValue<Integer> shownLine;
ArrowFactory(ObservableValue<Integer> shownLine) {
this.shownLine = shownLine;
}
#Override
public Node apply(int lineNumber) {
Polygon triangle = new Polygon(0.0, 0.0, 10.0, 5.0, 0.0, 10.0);
triangle.setFill(Color.GREEN);
ObservableValue<Boolean> visible = Val.map(
shownLine,
sl -> sl == lineNumber);
triangle.visibleProperty().bind(visible.conditionOnShowing(triangle));
return triangle;
}
}
And this is the result:
Working example
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.stage.Stage;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory;
import org.reactfx.value.Val;
import java.util.function.IntFunction;
public class CodeAreaWithLineIndicator extends Application {
CodeArea codeArea;
TextField textField;
public static final IntegerProperty lineValue = new SimpleIntegerProperty(-1) ;
/* public final int getValue() {
return value.get();
}*/
/* public final void setValue(int value) {
this.value.set(value);
}*/
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
codeArea = new CodeArea();
codeArea.replaceText(0,0,"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
codeArea.setPrefHeight(400);
IntFunction<Node> numberFactory = LineNumberFactory.get(codeArea);
IntFunction<Node> arrowFactory = new ManualArrowFactory(lineValue);
IntFunction<Node> graphicFactory = line -> {
HBox hbox = new HBox(
numberFactory.apply(line),
arrowFactory.apply(line));
hbox.setAlignment(Pos.CENTER_LEFT);
return hbox;
};
codeArea.setParagraphGraphicFactory(graphicFactory);
VBox vbox = new VBox();
textField = new TextField();
textField.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
try {
lineValue.setValue(Integer.parseInt(textField.getText()));
} catch (NumberFormatException e) {
}
}
});
Button button = new Button("MoveIt");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
try {
lineValue.setValue(Integer.parseInt(textField.getText()));
} catch (NumberFormatException e) {
}
}
});
vbox.getChildren().addAll(textField, button, codeArea);
Scene scene = new Scene(vbox, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
class ManualArrowFactory implements IntFunction<Node> {
private final IntegerProperty shownLine;
public ManualArrowFactory(IntegerProperty shownLine) {
this.shownLine = shownLine;
}
#Override
public Node apply(int lineNumber) {
Polygon triangle = new Polygon(0.0, 0.0, 10.0, 5.0, 0.0, 10.0);
triangle.setFill(Color.GREEN);
ObservableValue<Boolean> visible = Val.map(shownLine, sl -> sl.intValue()-1 == lineNumber);
triangle.visibleProperty().bind(
Val.flatMap(triangle.sceneProperty(), scene -> {
return scene != null ? visible : Val.constant(false);
}));
return triangle;
}
}
}
For multiline implementation:
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory;
import org.reactfx.value.Val;
import java.util.function.IntFunction;
public class CodeAreaWithLineIndicator extends Application {
CodeArea codeArea;
TextField textField;
public static final IntegerProperty lineValue = new SimpleIntegerProperty(-1) ;
public static final ObservableList<Integer> olistValue = FXCollections.observableArrayList();
public static final ListProperty<Integer> listValue = new SimpleListProperty<Integer>(olistValue);
/* public final int getValue() {
return value.get();
}*/
/* public final void setValue(int value) {
this.value.set(value);
}*/
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
codeArea = new CodeArea();
codeArea.replaceText(0,0,"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
codeArea.setPrefHeight(400);
IntFunction<Node> numberFactory = LineNumberFactory.get(codeArea);
IntFunction<Node> arrowFactory = new MultiBreakPointFactory(listValue);
IntFunction<Node> graphicFactory = line -> {
HBox hbox = new HBox(
numberFactory.apply(line),
arrowFactory.apply(line));
hbox.setAlignment(Pos.CENTER_LEFT);
return hbox;
};
codeArea.setParagraphGraphicFactory(graphicFactory);
VBox vbox = new VBox();
textField = new TextField();
textField.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
int newValue = Integer.parseInt(textField.getText());
olistValue.add(newValue);
}
});
Button button = new Button("Clear");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
olistValue.clear();
}
});
vbox.getChildren().addAll(textField, button, codeArea);
Scene scene = new Scene(vbox, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
class MultiBreakPointFactory implements IntFunction<Node> {
private final ListProperty<Integer> shownLines;
public MultiBreakPointFactory(ListProperty<Integer> shownLine) {
this.shownLines = shownLine;
}
#Override
public Node apply(int lineIndex) {
StackPane stackPane = new StackPane();
Circle circle = new Circle(10.0, 10.0, 6.0, Color.RED);
Rectangle rectangle = new Rectangle(20,20);
rectangle.setFill(Color.TRANSPARENT);
rectangle.setCursor(Cursor.HAND);
rectangle.setOnMouseClicked(me->{
if (!olistValue.contains(lineIndex+1)){
olistValue.add(lineIndex+1);
}
});
stackPane.getChildren().addAll(rectangle, circle);
circle.setOnMouseClicked(me->{
int index = olistValue.indexOf(lineIndex+1);
if (index>-1)
olistValue.remove(index);
});
circle.setCursor(Cursor.HAND);
ObservableValue<Boolean> visible = Val.map(shownLines, sl -> sl.contains(lineIndex+1));
circle.visibleProperty().bind(
Val.flatMap(circle.sceneProperty(), scene -> {
return scene != null ? visible : Val.constant(false);
}));
return stackPane;
}
}
}
Enter a number to textfield and click enter. Now only changing oListValue will show breakpoint lines on the codearea.
This is my sample code, In my project I have used scroll pane, but i am click outside of node and use arrow keys that nodes are move to Center,left,right,bottom.how to lock the node in same position,
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author reegan
*/
public class ComboBoxEditable extends Application {
Node sub;
#Override
public void start(Stage primaryStage) {
ComboBox mainCombo = new ComboBox(listofCombo());
Button save = new Button("Save");
sub = new ComboBox(listofCombo());
HBox root = new HBox(20);
root.getChildren().addAll(mainCombo, sub,save);
ScrollPane pane = new ScrollPane(root);
mainCombo.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
if (newValue == "Others") {
sub = new TextField();
} else {
sub = new ComboBox(listofCombo());
}
root.getChildren().remove(1);
root.getChildren().add(1, sub);
}
});
save.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println(mainCombo.getValue());
if(sub.getClass() == ComboBox.class) {
ComboBox sub1 = (ComboBox)sub;
System.out.println(sub1.getValue());
} else {
TextField field = (TextField)sub;
System.out.println(field.getText());
}
}
});
Scene scene = new Scene(pane, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public ObservableList listofCombo() {
ObservableList<String> list = FXCollections.observableArrayList();
for (int i = 0; i < 10; i++) {
list.add(String.valueOf("Hello" + i));
}
list.add("Others");
return list;
}
}
I am ref this example code :JavaFX: scrolling vs. focus traversal with arrow keys
#James_D told "The default behavior for a scroll pane is that, if it has keyboard focus, the cursor (arrow) keys will cause it to scroll".So consume that event Ref for this solution JavaFX: scrolling vs. focus traversal with arrow keys
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package comboboxeditable;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author reegan
*/
public class ComboBoxEditable extends Application {
Node sub;
#Override
public void start(Stage primaryStage) {
ComboBox mainCombo = new ComboBox(listofCombo());
Button save = new Button("Save");
sub = new ComboBox(listofCombo());
HBox root = new HBox(20);
root.getChildren().addAll(mainCombo, sub, save);
ScrollInterceptor pane = new ScrollInterceptor(root);
mainCombo.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
if (newValue == "Others") {
sub = new TextField();
} else {
sub = new ComboBox(listofCombo());
}
root.getChildren().remove(1);
root.getChildren().add(1, sub);
}
});
save.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println(mainCombo.getValue());
if (sub.getClass() == ComboBox.class) {
ComboBox sub1 = (ComboBox) sub;
System.out.println(sub1.getValue());
} else {
TextField field = (TextField) sub;
System.out.println(field.getText());
}
}
});
Scene scene = new Scene(pane, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public ObservableList listofCombo() {
ObservableList<String> list = FXCollections.observableArrayList();
for (int i = 0; i < 10; i++) {
list.add(String.valueOf("Hello" + i));
}
list.add("Others");
return list;
}
private static class ScrollInterceptor extends ScrollPane {
public ScrollInterceptor() {
remapArrowKeys(this);
}
public ScrollInterceptor(Node content) {
ScrollInterceptor.this.setContent(content);
remapArrowKeys(this);
}
private void remapArrowKeys(ScrollPane scrollPane) {
scrollPane.addEventFilter(KeyEvent.ANY, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
switch (event.getCode()) {
case UP:
case DOWN:
case LEFT:
case RIGHT:
event.consume();
}
}
});
}
}
}
Can someone write a short JavaFX example of a Popover from ControlFX ? I haven't been able to get it to work. Any help is greatly appreciated!
This answer is a simple use of ControlsFX's PopOver.
When the mouse moves over the Label the PopOver appears. When the mouse exits the Label the PopOver disappears.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.controlsfx.control.PopOver;
/**
*
* #author Sedrick
*/
public class JavaFXApplication35 extends Application {
#Override
public void start(Stage primaryStage) {
//Build PopOver look and feel
Label lblName = new Label("John Doe");
Label lblStreet = new Label("123 Hello Street");
Label lblCityStateZip = new Label("MadeUpCity, XX 55555");
VBox vBox = new VBox(lblName, lblStreet, lblCityStateZip);
//Create PopOver and add look and feel
PopOver popOver = new PopOver(vBox);
Label label = new Label("Mouse mouse over me");
label.setOnMouseEntered(mouseEvent -> {
//Show PopOver when mouse enters label
popOver.show(label);
});
label.setOnMouseExited(mouseEvent -> {
//Hide PopOver when mouse exits label
popOver.hide();
});
StackPane root = new StackPane();
root.getChildren().add(label);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
you must google out for that
many sites are available nowadays
https://bitbucket.org/controlsfx/controlsfx/commits/dca9619e05de26d176aaafe785c3b94f022562ef
https://bitbucket.org/controlsfx/controlsfx/pull-request/158/initial-commit-of-popover-control/activity
and etc. just Google out.
Here we have program known as "HelloPopOver".
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.ListView;
import javafx.scene.control.Slider;
import javafx.scene.control.TitledPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.TextAlignment;
import javafx.stage.Stage;
import org.controlsfx.ControlsFXSample;
import org.controlsfx.control.popover.PopOver;
import org.controlsfx.control.popover.PopOverController;
import org.controlsfx.control.popover.PopOverHeader;
import org.controlsfx.control.popover.PopOverTitledPane;
import org.controlsfx.samples.Utils;
public class HelloPopOver extends ControlsFXSample {
private PopOverController<PopOver, Button> controller = new MyController();
#Override
public Node getPanel(Stage stage) {
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setAlignment(Pos.CENTER);
grid.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent evt) {
controller.hidePopOver();
}
});
for (int i = 0; i < 10; i++) {
final Button button = new Button("Button " + i);
grid.add(button, i % 2, i / 2);
button.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent evt) {
controller.hidePopOver();
if (evt.getClickCount() == 2) {
controller.setupPopOver(button);
controller.showPopOver(button, evt.getScreenX(),
evt.getScreenY());
}
}
});
}
return grid;
}
class MyController extends PopOverController<PopOver, Button> {
#Override
protected PopOver createPopOver(final Button button) {
PopOver editor = new PopOver();
PopOverHeader<?> header = (PopOverHeader<?>) editor.getHeader();
header.setTitle(button.getText() + " (edit me)");
header.setSubtitle("Just some random controls (edit me)");
editor.setDetachedTitle(button.getText());
editor.getPanes().add(createTitledPane("Start Time & Duration"));
editor.getPanes().add(createTitledPane("Dependencies"));
editor.getPanes().add(createTitledPane("Priority"));
editor.getPanes().add(createTitledPane("Assignments / Resources"));
editor.setExpandedPane(editor.getPanes().get(0));
editor.setFooter(new Footer());
ColorPicker picker = (ColorPicker) header.getExtra();
picker.valueProperty().addListener(new ChangeListener<Color>() {
#Override
public void changed(ObservableValue<? extends Color> value,
Color oldColor, Color newColor) {
button.setBackground(new Background(new BackgroundFill(
newColor, CornerRadii.EMPTY, Insets.EMPTY)));
}
});
return editor;
}
}
private TitledPane createTitledPane(String title) {
VBox box = new VBox(5);
box.getChildren().add(new Button("Test"));
box.getChildren().add(new Slider());
ListView<String> view = new ListView<>();
view.setPrefHeight(100);
box.getChildren().add(view);
final TitledPane pane = new PopOverTitledPane(title, box);
pane.setTextAlignment(TextAlignment.LEFT);
Pane connectivityArrow = (Pane) pane.lookup(".arrow");
if (connectivityArrow != null) {
connectivityArrow.translateXProperty().bind(
pane.widthProperty().subtract(
connectivityArrow.widthProperty().multiply(2)));
}
return pane;
}
class Footer extends FlowPane {
public Footer() {
super(Orientation.HORIZONTAL);
setAlignment(Pos.CENTER_RIGHT);
Button delete = new Button("Delete");
getChildren().add(delete);
delete.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent evt) {
}
});
}
}
public static void main(String[] args) {
Application.launch(args);
}
#Override
public String getSampleName() {
return "PopOver";
}
#Override
public String getJavaDocURL() {
return Utils.JAVADOC_BASE
+ "org/controlsfx/control/popover/PopOver.html";
}
#Override
public String getSampleDescription() {
return "An implementation of a pop over control as used by Apple for its iCal application. A pop over allows"
+ "the user to see and edit an objects properties. The pop over gets displayed in its own popup window and"
+ "can be torn off in order to create several instances of it.";
}
}