I'm new to JavaFX and was wondering if the Bindings API allowed an easier way to achieve the following. Consider a model that contains a database that may be null (because the database loads asynchronously) and a view that displays a label status reflecting the state of the database. If it is null it should say something like "Loading..." and if it isn't it should display how many items are in the database. It also would be great if the status could reflect the size of the database as it grows or shrinks.
So far, I understand that I could bind an integer property (size of the database) to the text property of the label by using a converter. This is fine, but I want the label to display more than the number. A localized string like "Loaded {0} items" precisely. And let's not forget that the database may still be null.
This is the solution I have in place
#Override
public void initialize(URL url, ResourceBundle bundle) {
// Initialize label with default value
status();
model.databaseProperty().addListener((obs, old, neu) -> {
// Update label when database is no longer null
status();
// Update label when size of database changes
neu.sizeProperty().addListener(x -> status());
});
}
public void status() {
if (model.database() == null) {
status.setText(bundle.getString("status.loading"));
} else {
String text = bundle.getString("status.ready");
int size = model.database().size();
text = new MessageFormat(text).format(size);
status.setText(text);
}
}
It works, but is there a way to do it with a chain of bindings, or at least part of it? I've seen how powerful (and lenghty) boolean bindings can be but I'm not sure something as flexible is possible with string bindings.
You can use Bindings.when, which is essentially a dynamic if/then binding:*
status.textProperty().bind(
Bindings.when(model.databaseProperty().isNull())
.then(bundle.getString("status.loading"))
.otherwise(
Bindings.selectInteger(model.databaseProperty(), "size").asString(
bundle.getString("status.ready")))
);
However, the above assumes bundle.getString("status.ready") returns a java.util.Formatter string, not a MessageFormat string. In other words, it would need to be "Loaded %,d items" rather than "Loaded {0,number,integer} items".
Bindings doesn’t have built-in support for MessageFormat, but if you really want to stick with MessageFormat (which is a legitimate requirement, as there are things MessageFormat can do which Formatter cannot), you can create a custom binding with Bindings.createStringBinding:
MessageFormat statusFormat = new MessageFormat(bundle.getString("status.ready"));
status.textProperty().bind(
Bindings.when(model.databaseProperty().isNull())
.then(bundle.getString("status.loading"))
.otherwise(
Bindings.createStringBinding(
() -> statusFormat.format(new Object[] { model.getDatabase().getSize() }),
model.databaseProperty(),
Bindings.selectInteger(model.databaseProperty(), "size")))
);
* Actually, it’s more like the ternary ?…: operator.
Related
I am tring to show tooltip for OMPoint ( com.bbn.openmap.omGraphics.OMPoint )
OMPoint is in layer (OMGraphicHandlerLayer). I do:
layer.setToolTipTex("text");
In this way tooltip is showing in all layer.
Do you have any advice because i can't do:
point.setToolTipTex("text");
because OMpoint not hereditary to OMGraphicHandlerLayer
You need to add logic similar to the following to your layer:
#Override
public String getToolTipTextFor(OMGraphic graphic)
{
Object value = graphic.getAttribute(OMGraphic.TOOLTIP);
if (value instanceof String)
{
return (String) value;
}
return null;
}
The use of the graphic attributes to store the tool tip is optional so long as this method returns the text you want to display.
The map needs to have an InfoDisplayListener registered to it to handle the display of the tool tip on the map. I used the built-in InformationDelegator map component. This provides a number of other UI elements which you turn off if you so choose.
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'm trying to create a Java GUI dynamically by taking values from a result set and using it to generate a checklist. I've created a small demo program to demonstrate what I've done:
SQL Commands
CREATE USER 'test'#'localhost' IDENTIFIED BY 'testpw';
CREATE DATABASE combotest;
USE combotest;
CREATE TABLE combotable (
id INT(5) NOT NULL PRIMARY KEY auto_increment,
type VARCHAR(50) NOT NULL);
INSERT INTO combotable (id, type) VALUES
(default, 'Label'),
(default, 'Textfield'),
(default, 'Combo'),
(default, 'Label'),
(default, 'Textfield'),
(default, 'Combo'),
(default, 'Combo');
GRANT SELECT ON combotest.* TO 'test'#'localhost';
For your convenience if you'd like to test it yourself I've put all the SQL commands above.
Now, for my Java code:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.*;
import javax.swing.*;
public class resToComboDemo implements ActionListener {
//JDBC Variables
static Connection connect = null;
static Statement statement = null;
static ResultSet res = null;
#SuppressWarnings("rawtypes")
//Other Variables
JComboBox comboBox;
JButton submit;
JFrame frame;
JLabel label;
JTextField textField;
Container pane;
public static void main(String[] args) throws SQLException {
new resToComboDemo();
}
public resToComboDemo() throws SQLException {
try {
Class.forName("com.mysql.jdbc.Driver");
// Setup the connection with the DB
connect = DriverManager
.getConnection("jdbc:mysql://localhost/combotest?"
+ "user=test&password=testpw");
statement = connect.createStatement();
//Note: in this specific case I do realize that "order by id" is not necessary. I want it there, though.
res = statement.executeQuery("SELECT * FROM combotable ORDER BY id");
createStuff(res);
} catch (Exception e) {
JOptionPane.showMessageDialog(null, "Error 1: "+e, "Error!", JOptionPane.ERROR_MESSAGE);
} finally {
connect.close();
}
}
#SuppressWarnings({"rawtypes", "unchecked" })
public void createStuff (ResultSet res) throws SQLException {
frame = new JFrame("Testing dynamic gui");
Dimension sD = Toolkit.getDefaultToolkit().getScreenSize();
int width = sD.width;
int height = sD.height - 45;
frame.setSize(width,height);
pane = frame.getContentPane();
pane.setLayout(new GridLayout(0, 2));
while (res.next()) {
Object[] options = { "Pass", "Fail"};
String type = res.getString("type");
JLabel label = new JLabel("<html><small>"+type+"</small></html>");
JLabel blank = new JLabel(" ");
blank.setBackground(Color.black);
blank.setOpaque(true);
if (type.equals("Label")) {
label.setBackground(Color.black);
label.setForeground(Color.white);
label.setOpaque(true);
pane.add(label);
pane.add(blank);
} else if (type.equals("Combo")) {
pane.add(label);
comboBox = new JComboBox(options);
pane.add(comboBox);
} else if (type.equals("Textfield")) {
pane.add(label);
textField = new JTextField(20);
pane.add(textField);
}
}
JLabel blank2 = new JLabel(" ");
pane.add(blank2);
submit = new JButton("Submit");
submit.addActionListener(this);
pane.add(submit);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent e) {
}
}
Now, everything works great with creating the GUI here. However, I need to be able to treat the Combobox and Textfield components as their own separate entities. Meaning, I want to be able to get user input from each different component. Right now, if I were to request information from textfield, it just gives me the information from the last textfield. This makes perfect since, because that's how java reads it. I have no problem with that.
I just can't for the life of me figure out how to get each component's input separately. Perhaps by taking the result set and adding the results to some type of array? I've attempted this multiple times in different flavors and I can't get it to come out the way I need it to. Some of you are going to request that I show you what I've tried... but honestly, it's not worth it.
And, before anybody asks: No, I will not use FlowLayout. :)
Any help is greatly appreciated!
There are probably a few ways to achieve this based on what you want to do...
If you are only performing a batch update, you could use a Map keyed to the id of the row and mapping to the Component.
This way, when you want to save the values back to the database, you would simply iterate the Maps key values, extract the Component associated with each key and then extract the value of the Component...
I might consider making a wrapper interface which has a simple getText method and wrap the component within it, making the implementation of the wrapper responsible for extracting the text, but that's just me ;)
If you want to perform updates when a individual component is updated, you would need to swap the mapping, so that the Component would the key and the id would be mapped to it.
This would mean that when some kind of event occurred that would trigger and update (ie a ActionEvent), you could extract the source from the event and look up the id in the Map based on the Component that caused the event...
Now...frankly, I would simply use a JTable and create a custom TableModel which could model all this.
This would require you to create POJO of the table, maintaining the id, type and value within a single object. This would define a basic row in the table.
The only problem is you would need to create a (reasonably) complex TableCellEditor that could take the type and return an appropriate editor for the table. Not impossible, it's just an additional complexity beyond the normal usage of a table.
This would all the information you need is available in a single object of a single row in the table.
Take a look at How to use tables for more details
Equally, you could use a similarly idea with the Map ideas above...
You could also simply create a self contained "editor" (extending from something like JPanel), which maintain information about the id and type and from which you could extract the value and simply keep a list of these....for example...
what about interrogating the Container ( pane) which contains the components
getComponents() method and loop through the sub component and check for JComobox and JTextField do the required cast and retrieve the value
Just an idea in case you are against adding the sub-components into a kind of list
You only have a reference to the last text field or combo box that you create, since you are reusing the variables that hold them. I would put them in an ArrayList, store each new text field and combbox as you create them, then you can go back and get input from all of them after you're done.
---------- (after the OP's response to the above paragraph)
No, there is no "place to refer you" -- it's your set of requirements, it would be pretty remarkable to find code that already existed that did this exact thing. Java and Swing give you the tools, you need to put things together yourself.
You don't show your "actionPerformed" routine, but let's hypothesize about it for a minute. It is called by the framework when an action is done, and it is passed an "ActionEvent" object. Looking through its methods, we find that it has "getSource()", so it will give you a reference to the component which generated the event.
Let's further think about what we have -- a set of components in the UI, and ones which can generate events are interesting to us. We want to, in this case, retrieve something from the component that generated the event.
If we have the component (from actionEvent.getSource()) and we want to do something with it, then we can, at worst do something like the following in the actionPerformed() method:
Component sourceComponent = actionEvent.getSource();
if (sourceComponent instanceof JComboBox)
{ JComboBox sourceBox = (JComboBox) sourceComponent;
// get the value from the combo box here
}
else if (sourceComponent instanceof JTextField)
{ JTextField sourceTextField = (JTextField) sourceComponent;
// get the value from the text field here
}
// or else do nothing -- our action was not one of these.
Done this way, you don't even need to keep a list of the components -- the UI is keeping a reference to all of them, and you just use that reference when the actionEvent occurs.
Now, this is not the only or even the best or the simplest way of doing this. If you wanted to extend JComboBox and JTextField with your own classes, you could have those classes both implement an interface that defined something like getValue() or getText; then you would not need the ugly instance of operator, which can usually be done away with by better design and planning.
I'm trying to pick up java quickly and looking for a way to set the text of a number of labels in my java app.
What I have is a java app that starts\stops\checks status of windows services. I have a method, which is passed an array of these service names and each of these services has a corresponding label that contains it's status. For example, DummyService1 is contained in the array and there is a label called txt_DummyService1. My method (short version) does the following
public static void Checker(String Array[])
{
//check status of DummyService1
"txt_"+DummyService.Text = "started";
}
I realize that this isn't the way that you do this, but could anybody help me out with the best way to do this?
There's no way to generate a "variable" name from a String in this manner. Yes, you might use reflection, but that already rasies questions about the quality of the design.
Instead. Place each label into a Map keyed by it's name.
private Map<String, JLabel> labelLookup = new HashMap<>(25); // Instance variable.
In you constructor (or where ever you build your UI), add each label to the Map.
/* Other UI code */
labelLookup.put("DummyService1", txt_DummyService1);
Now, when you need to do you changes, simply look up the label by it's name
// You had better have a VERY good reason for making this static...
public void checker(String services[])
{
for (String service : services) {
JLabel label = labelLookup.get(service);
if (label != null) {
label.setText("Started");
}
}
}
For example...
Actually I was looking for something more like the following
public static void Checker()
{
try
{
Object Instance = getClass().getDeclaredField("txt_DummyService").get(this);
Method m = Instance.getClass().getMethod("setText",String.class);
m.invoke(Instance,"started");
}
catch(Exception e)
{
//exception handling
}
}
You cannot manipulate variable names at runtime since these are only available to the compiler. One solution to your problem is to keep a Map<String, JLabel> (assuming you are using JLabel and not some other component) to associate a name with each JLabel. I'm sure there are several other possible solutions depending on the exact design of your code.
We have a Virtual Table in my Eclipse RCP application. We make a call to the backend to retrieve the data to be populated in the virtual table.
We want default sorting on the table on a single column. We use ViewerComparator to achieve sorting functionality. My problem is, I am not able to get this sorting working when the table loads with the data for the 1st time. But when I click on the column, everything works fine as expected.
This is how, I set the Comparator to the column
TableViewerColumn tvc = viewer.addColumn(100, SWT.LEFT, "Name");
viewer.setColumnComparator(tvc,
new Comparator<Person>() {
#Override
public int compare(Person o1,Person o2) {
double firstValue = Double.parseDouble(o1
.getAge());
double secondValue = Double.parseDouble(o2
.getAge());
return firstValue > secondValue ? 1 : -1;
}
});
setColumnComparator method in custom viewer
public void setColumnComparator(TableViewerColumn tvc, Comparator<T> cmp){
final MyViewerComparator c = new MyViewerComparator(cmp);
final TableColumn tc = tvc.getColumn();
setComparator(c);
getTable().setSortDirection(c.getDirection());
getTable().setSortColumn(tc);
refresh();
tc.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
<same code as above>
}
});
MyViewerComparator
class MyViewerComparator extends ViewerComparator{
Comparator<T> cmp;
boolean desc = true;
MyViewerComparator(Comparator<T> cmp){
this.cmp = cmp;
}
int getDirection(){
return desc?SWT.UP:SWT.DOWN;
}
void flipDirection(){
desc = !desc;
}
#Override
public int compare(Viewer viewer, Object e1, Object e2) {
if(e1 == null || e2==null){
return 0;
}
int rc = cmp.compare((T)e1, (T)e2);
if(desc)
return -rc;
return rc;
}
}
When the table loads the data for the 1st time, it goes inside the Bolded condition in the above code as one of the object is ALWAYS NULL
Note: This functionality works totally fine if I use a Standard table rather than VIRTUAL TABLE. I am not sure whether I can change it to use Standard table as we want the lazy load functionality as well..
ContentProvider used is: ObservableListContentProvider
Please advise..
A late answer that hopefully still helps others. I encountered exactly the same problem when using SWT.VIRTUAL with an ObservableListContentProvider in combination with sorting.
The original intent of SWT.VIRTUAL is that not all elements in the contents need to be fetched to show only part of the contents. A custom content provider needs to be implemented which only has to return the elements that need to be currently shown on the screen. You also have to tell the table the total number of elements in existence. In such a use case, a table cannot be sorted in the normal way with a ViewerComparator because not all elements are known. However SWT.VIRTUAL can also be used as a performance optimization for rendering a table with many elements. This seems to work fine with the non-observable ArrayContentProvider.
But when using ObservableListContentProvider I am seeing exactly the same issue as you have. Somehow it tries to be smart and update only the elements that have actually changed. Somewhere in the depths of it's implementation something goes wrong for virtual tables, I have no clue exactly what. But I do have a solution: don't use ObservableListContentProvider at all and simply refresh the table viewer. You can e.g. use a plain ArrayContentProvider and add the following listener to the IObservableList contents of the viewer:
new IListChangeListener() {
#Override
public void handleListChange(ListChangeEvent event) {
viewer.refresh();
}
};
I actually implemented my own "SimpleObservableListContentProvider" that does exactly this, but also takes care of switching table input by implementing the inputChanged method to remove this listener from the old input list and add it to the new one.