How do I make part of a TreeViewer cell bold? - java

I currently want to write an Eclipse editor based on a JFace TreeViewer. I added a CellLabelProvider to the TreeViewer. If I set the font of a cell with directly in the update method of the CellLabelProvider to a bold font the label is shown bold.
But I want only part of the label to be shown as bold. So I apply StyleRanges to the cell. Selected colors in the 'StyleRange's work perfectly, but setting the font of a StyleRange to a bold one, does not seem to work.
Why is that and how do I fix it?

As specified by greg-449, essentially you could create your Font and set it as bold:
class BoldFontStyler extends Styler {
#Override
public void applyStyles(final TextStyle textStyle)
{
FontDescriptor boldDescriptor = FontDescriptor.createFrom(new FontData()).setStyle(SWT.BOLD);
Font boldFont = boldDescriptor.createFont(Display.getCurrent());
textStyle.font = boldFont;
}
}
Then you can apply that to your StyledString inside #getStyledText().
I am not sure this is portable, I just tested here on my Linux machine.
A full example (just attach it to a Part):
package com.ebotlution.c4ids.comms.ui;
import javax.annotation.PostConstruct;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.StyledString.Styler;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.TextStyle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.TreeColumn;
class BoldFontStyler extends Styler {
#Override
public void applyStyles(final TextStyle textStyle)
{
FontDescriptor boldDescriptor = FontDescriptor.createFrom(new FontData()).setStyle(SWT.BOLD);
Font boldFont = boldDescriptor.createFont(Display.getCurrent());
textStyle.font = boldFont;
}
}
class NormalColumnLabelProvider extends ColumnLabelProvider {
#Override
public String getText(Object element) {
return (element).toString().split(" ")[0];
}
}
class BoldColumnLabelProvider extends DelegatingStyledCellLabelProvider {
static final Styler BOLD_FONT_STYLER = new BoldFontStyler();
public BoldColumnLabelProvider() {
super(new IStyledLabelProvider() {
#Override
public Image getImage(Object element) {
return null;
}
#Override
public StyledString getStyledText(Object element) {
return new StyledString((element).toString().split(" ")[1], BOLD_FONT_STYLER);
}
#Override
public void addListener(ILabelProviderListener listener) {}
#Override
public void dispose() {}
#Override
public boolean isLabelProperty(Object element, String property) {
return false;
}
#Override
public void removeListener(ILabelProviderListener listener) {}
});
}
}
class MyContentProvider implements ITreeContentProvider {
static final Object[] EMPTY_ARRAY = new Object[0];
Object[] data;
#Override
public void dispose() {}
#Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
data = (Object[]) newInput;
}
#Override
public Object[] getElements(Object inputElement) {
return data;
}
#Override
public Object[] getChildren(Object parentElement) {
return EMPTY_ARRAY;
}
#Override
public Object getParent(Object element) {
return null;
}
#Override
public boolean hasChildren(Object element) {
return false;
}
}
public class BoldColumnTreeViewerEx {
TreeViewer treeViewer;
#PostConstruct
public void createControls(Composite parent) {
treeViewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
treeViewer.setUseHashlookup(true);
treeViewer.setContentProvider(new MyContentProvider());
// Do all your stuff
// [...]
TreeViewerColumn normalCol = createTreeViewerColumn("Normal Column");
normalCol.setLabelProvider(new NormalColumnLabelProvider());
TreeViewerColumn boldCol = createTreeViewerColumn("Bold Column");
boldCol.setLabelProvider(new BoldColumnLabelProvider());
treeViewer.setInput(new String[] { "Hello Dude", "SWT JFace" });
treeViewer.expandAll();
}
private TreeViewerColumn createTreeViewerColumn(String title) {
TreeViewerColumn viewerColumn = new TreeViewerColumn(treeViewer, SWT.NONE);
TreeColumn column = viewerColumn.getColumn();
column.setText(title);
column.setWidth(100);
return viewerColumn;
}
}

You probably need to use a label provider based on StyledCellLabelProvider.
DelegatingStyledCellLabelProvider is easiest to use because you only need to provide a label provider implementing DelegatingStyledCellLabelProvider.IStyledLabelProvider. The key method in this is
public StyledString getStyledText(Object element);
which lets you just return a StyledString for each object.

Related

How do I make Java jtables, cell editors and undo work together w/o creating extranious undo event?

In the following code, I create a jtable with a custom cell editor for the first column and then add undo capabilities to the table. When you run the program, the program allows you to change the values in the first column (test by appending a "d" and then an "e" to the "abc" already there). Now enter control-z (undo) and enter control-z again. It works as expected. But now enter control-z (undo) again. This time the "abc" is erased. It looks like the swing system is setting the initial value of the column and creating an undo event for that action which the user can then undo. My question - how do I write my code so that the user only can undo the actions the user makes?
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.DefaultCellEditor;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JRootPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;
public class UndoExample extends JFrame {
private static final long serialVersionUID = 1L;;
static Boolean objEnableUndoRedoActions = true;
UndoExample rootFrame;
public UndoExample() {
// This procedure starts the whole thing off.
//Create table
final String[] tableColumns = {"Column 1", "Column 2"};
JTable tabUndoExample = new JTable(
new DefaultTableModel(null, tableColumns) {
private static final long serialVersionUID = 1L;
});
final DefaultTableModel tabUndoExampleModel = (DefaultTableModel) tabUndoExample
.getModel();
tabUndoExampleModel.addRow(new Object[]{"abc", true});
tabUndoExampleModel.addRow(new Object[]{"zyw", false});
// Create the undo/redo manager
UndoManager objUndoManager = new UndoManager();
// Create a cell editor
JTextField tfTabField = new JTextField();
TableCellEditor objEditor = new DefaultCellEditor(tfTabField);
// Make the cell editor the default editor for this table's first column
tabUndoExample.getColumnModel().getColumn(0)
.setCellEditor(objEditor);
// Create the undo action on the field's document for the column
tfTabField.getDocument().addUndoableEditListener(
new uelUndoRedoTableCellField(objUndoManager, tabUndoExample));
// Allow undo and redo to be entered by the user
UndoRedoSetKeys(this, "Example", objUndoManager);
tabUndoExample.setInheritsPopupMenu(true);
//Add the table to the frame and show the frame
this.add(tabUndoExample);
this.pack();
setLocationRelativeTo(null);
}
public static void main(final String[] args) {
// Launches the application. This is required syntax.
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
final UndoExample rootFrame = new UndoExample();
rootFrame.setVisible(true);
} catch (final Exception e) {
}
}
});
}
#SuppressWarnings("serial")
static class aueUndoRedoTableCellField extends AbstractUndoableEdit {
// Wrap the text edit action item as we need to add the table
// row and column information. This code is invoked when the
// code sees an undo event created and then later when the
// user requests an undo/redo.
JTable objTable = null;
UndoableEdit objUndoableEdit;
int objCol = -1;
int objRow = -1;
public aueUndoRedoTableCellField(UndoableEdit undoableEdit,
JTable table, int row, int col) {
super();
objUndoableEdit = undoableEdit;
objTable = table;
objCol = col;
objRow = row;
}
public void redo() throws CannotRedoException {
// When the user enters redo (or undo), this code sets
// that we are doing an redo (or undo), sets the cursor
// to the right location, and then does the undo (or redo)
// to the table cell.
UndoRedoManagerSetEnabled(false);
super.redo();
#SuppressWarnings("unused")
boolean success = objTable.editCellAt(objRow, objCol);
objTable.changeSelection(objRow, objCol, false, false);
objUndoableEdit.redo();
UndoRedoManagerSetEnabled(true);
}
public void undo() throws CannotUndoException {
super.undo();
UndoRedoManagerSetEnabled(false);
#SuppressWarnings("unused")
boolean success = objTable.editCellAt(objRow, objCol);
objTable.changeSelection(objRow, objCol, false, false);
objUndoableEdit.undo();
UndoRedoManagerSetEnabled(true);
}
}
static class aUndoRedo extends AbstractAction {
// This code is bound to the undo/redo keystrokes and tells
// Java what commands to run when the keys are later entered
// by the user.
private static final long serialVersionUID = 1L;
Boolean objUndo = true;
UndoManager objUndoManager = null;
final String objLocation;
public aUndoRedo(Boolean Undo, UndoManager undoManager, String location) {
super();
objUndo = Undo;
objUndoManager = undoManager;
objLocation = location;
}
#Override
public void actionPerformed(ActionEvent ae) {
try {
// See if operation allowed
if (!objUndoManager.canUndo() && objUndo
|| !objUndoManager.canRedo() && !objUndo)
return;
UndoRedoManagerSetEnabled(false);
if (objUndo) {
objUndoManager.undo();
} else {
objUndoManager.redo();
}
UndoRedoManagerSetEnabled(true);
// Catch errors and let user know
} catch (Exception e) {
UndoRedoManagerSetEnabled(true);
}
}
}
static class uelUndoRedoTableCellField implements UndoableEditListener {
// This action is called when the user changes the table's
// text cell. It saves the change for later undo/redo.
private UndoManager objUndoManager = null;
private JTable objTable = null;
public uelUndoRedoTableCellField(UndoManager undoManager,
JTable table) {
objUndoManager = undoManager;
objTable = table;
}
#Override
public void undoableEditHappened(UndoableEditEvent e) {
// Remember the edit but only if the code isn't doing
// an undo or redo currently.
if (UndoRedoManagerIsEnabled()) {
objUndoManager.addEdit(new aueUndoRedoTableCellField(e
.getEdit(), objTable, objTable.getSelectedRow(),
objTable.getSelectedColumn()));
}
}
}
static public Boolean UndoRedoManagerIsEnabled() {
// See if we are currently doing an undo/redo.
// Return true if so.
return objEnableUndoRedoActions;
}
static public void UndoRedoManagerSetEnabled(Boolean state) {
// Set the state of whether we are in undo/redo code.
objEnableUndoRedoActions = state;
}
static void UndoRedoSetKeys(JFrame frame, final String location, UndoManager undoManager) {
// Allow undo and redo to be called via these keystrokes for this dialog
final String cntl_y = "CNTL_Y";
final KeyStroke ksCntlY = KeyStroke.getKeyStroke("control Y");
final String cntl_z = "CNTL_Z";
final KeyStroke ksCntlZ = KeyStroke.getKeyStroke("control Z");
JRootPane root = frame.getRootPane();
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(ksCntlZ, cntl_z);
root.getActionMap().put(
cntl_z,
new aUndoRedo(true, undoManager, location));
root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(ksCntlY, cntl_y);
root.getActionMap().put(
cntl_y,
new aUndoRedo(false, undoManager, location));
}
}
When you press a key, a series of things occur. The JTable, process the key stroke, it checks to see if the cell is editable (as the TableModel), it then asks the editor for the currently selected cell if the event should edit the cell (CellEditor#isCellEditable(EventObject)).
If this method returns true, the editor is prepared, the value from the TableModel is applied to the editor (ie setText is called), and the editor is added to the JTable, finally, the event which triggered the edit mode is re-dispatched to the editor, in your case the Ctrl+Z, which then triggers and undo event, returning the editor it's initial state (before setText was called).
You can try and use something like...
TableCellEditor objEditor = new DefaultCellEditor(tfTabField) {
#Override
public boolean isCellEditable(EventObject anEvent) {
boolean isEditable = super.isCellEditable(anEvent); //To change body of generated methods, choose Tools | Templates.
if (isEditable && anEvent instanceof KeyEvent) {
KeyEvent ke = (KeyEvent) anEvent;
if (ke.isControlDown() && ke.getKeyCode() == KeyEvent.VK_Z) {
isEditable = false;
}
}
return isEditable;
}
};
to prevent the JTable from been placed into edit when a specific key stroke occurs
Updated
So based on Andrew's answer from JTextArea setText() & UndoManager, I devised a "configurable" UndoableEditListener which can be set to ignore undoable actions, for example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
public class FixedField {
public static void main(String[] args) {
new FixedField();
}
public FixedField() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class UndoableEditHandler implements UndoableEditListener {
private static final int MASK
= Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
private UndoManager undoManager = new UndoManager();
private boolean canUndo = true;
public UndoableEditHandler(JTextField field) {
Document doc = field.getDocument();
doc.addUndoableEditListener(this);
field.getActionMap().put("Undo", new AbstractAction("Undo") {
#Override
public void actionPerformed(ActionEvent evt) {
try {
if (undoManager.canUndo()) {
undoManager.undo();
}
} catch (CannotUndoException e) {
System.out.println(e);
}
}
});
field.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, MASK), "Undo");
field.getActionMap().put("Redo", new AbstractAction("Redo") {
#Override
public void actionPerformed(ActionEvent evt) {
try {
if (undoManager.canRedo()) {
undoManager.redo();
}
} catch (CannotRedoException e) {
System.out.println(e);
}
}
});
field.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, MASK), "Redo");
}
#Override
public void undoableEditHappened(UndoableEditEvent e) {
if (canUndo()) {
undoManager.addEdit(e.getEdit());
}
}
public void setCanUndo(boolean canUndo) {
this.canUndo = canUndo;
}
public boolean canUndo() {
return canUndo;
}
}
public class TestPane extends JPanel {
public TestPane() {
JTextField field = new JTextField(10);
UndoableEditHandler handler = new UndoableEditHandler(field);
handler.setCanUndo(false);
field.setText("Help");
handler.setCanUndo(true);
add(field);
}
}
}
Now, you're going to have to devices your own TableCellEditor to support this, for example...
public static class MyCellEditor extends AbstractCellEditor implements TableCellEditor {
private JTextField editor;
private UndoableEditHandler undoableEditHandler;
public MyCellEditor(JTextField editor) {
this.editor = editor;
undoableEditHandler = new UndoableEditHandler(editor);
editor.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fireEditingStopped();
}
});
}
#Override
public Object getCellEditorValue() {
return editor.getText();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
undoableEditHandler.setCanUndo(false);
editor.setText(value == null ? null : value.toString());
undoableEditHandler.setCanUndo(true);
return editor;
}
}

Best practice to decorate an ObservableList and retain change events

My datasource provides an ObservableList<String>, however for my ListView I need an ObservableList<Warning>.
A Warning is basically just a decorator for the strings, adding a boolean to provide a means for tracking the state of checkboxes my ListView, as proposed in this answer.
class Warning {
private final ReadOnlyStringWrapper name;
private final BooleanProperty checked;
/* ... */
}
Currently I am observing change-events in the original list and add/remove items in the warnings list manually:
ObservableList<String> stringList = ...;
ObservableList<Warning> warningList = ...;
stringList.addListener(new ListChangeListener<String>() {
#Override
public void onChanged(ListChangeListener.Change<? extends String> change) {
if (change.wasAdded()) {
warningList.addAll(change.getAddedSubList().stream().map(Warning::new).collect(Collectors.toList()));
} else if (change.wasRemoved()) {
change.getRemoved().forEach(str -> {
warningList.removeIf(w -> str.equals(w.name));
});
}
}
});
My question is: Is there a more elegant way to decorate my String-typed list, so it can be used as a Warning-typed list without manually passing through change events?
To be more precise: If a string is added to or removed from the original list, I want to see this change immediately in the Warnings-list and thus the ListView.
I've been thinking about this a bit since you posted it. Using EasyBind as I had suggested in the comment won't work, since it would create a new Warning every time you called get(...) on the mapped list. So
stringList.add("foo");
warningList.get(0).setChecked(true);
assert warningList.get(0).isChecked();
would fail.
Additionally, your mechanism goes wrong (I think) if you have duplicate entries in the source list (stringList), as you would remove all corresponding entries from the warningList when a single element was removed from the stringList. In fact, getting the removed elements correct is quite tricky.
Here is a solution based on Tomas Mikula's MappedList which caches the mapping between the source elements and the mapped elements. It uses an IdentityHashMap to ensure that duplicate elements behave correctly in both lists. Note this only works for the specific case where you want to create new objects when items are added to the source list, so it is not intended (and would not work) as a replacement for the mechanism in EasyBind.
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.transformation.TransformationList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class WrappedObjectListExample extends Application {
#Override
public void start(Stage primaryStage) {
ObservableList<String> stringList = FXCollections.observableArrayList("One", "Two", "Three");
ObservableList<Warning> warningList = new CachingMappedList<Warning, String>(stringList, Warning::new);
ListView<String> stringListView = new ListView<>(stringList);
ListView<Warning> warningListView = new ListView<>(warningList);
warningListView.setCellFactory(CheckBoxListCell.forListView(Warning::checkedProperty));
TextField textField = new TextField();
textField.setOnAction(e -> {
if (! textField.getText().isEmpty()) {
stringList.add(textField.getText());
textField.setText("");
}
});
Button remove = new Button("Remove");
remove.setOnAction(e -> stringList.remove(stringListView.getSelectionModel().getSelectedIndex()));
remove.disableProperty().bind(stringListView.getSelectionModel().selectedItemProperty().isNull());
HBox lists = new HBox(10, stringListView, warningListView);
VBox root = new VBox(10, lists, textField, remove);
root.setPadding(new Insets(20));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static class Warning {
private final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();
private final BooleanProperty checked = new SimpleBooleanProperty();
public Warning(String name) {
this.name.set(name);
}
public final ReadOnlyStringProperty nameProperty() {
return this.name.getReadOnlyProperty();
}
public final String getName() {
return this.nameProperty().get();
}
public final BooleanProperty checkedProperty() {
return this.checked;
}
public final boolean isChecked() {
return this.checkedProperty().get();
}
public final void setChecked(final boolean checked) {
this.checkedProperty().set(checked);
}
#Override
public String toString() {
return getName();
}
}
public static class CachingMappedList<S,T> extends TransformationList<S, T> {
private final Function<T, S> mapper ;
private final IdentityHashMap<T, S> cache ;
public CachingMappedList(ObservableList<T> source, Function<T,S> mapper) {
super(source);
this.mapper = mapper ;
this.cache = new IdentityHashMap<>();
}
#Override
protected void sourceChanged(Change<? extends T> c) {
fireChange(new Change<S>(this) {
#Override
public boolean wasAdded() {
return c.wasAdded();
}
#Override
public boolean wasRemoved() {
return c.wasRemoved();
}
#Override
public boolean wasReplaced() {
return c.wasReplaced();
}
#Override
public boolean wasUpdated() {
return c.wasUpdated();
}
#Override
public boolean wasPermutated() {
return c.wasPermutated();
}
#Override
public boolean next() {
return c.next();
}
#Override
public void reset() {
c.reset();
}
#Override
public int getFrom() {
return c.getFrom();
}
#Override
public int getTo() {
return c.getTo();
}
#Override
public List<S> getRemoved() {
List<S> removed = new ArrayList<>();
c.getRemoved().forEach(t -> removed.add(cache.get(t)));
return removed;
}
#Override
public int getPermutation(int i) {
return c.getPermutation(i);
}
#Override
protected int[] getPermutation() {
throw new AssertionError("Unreachable code");
}
});
// clean up cache:
c.reset();
while (c.next()) {
if (c.wasRemoved()) {
c.getRemoved().forEach(cache::remove);
}
}
}
#Override
public int getSourceIndex(int index) {
return index ;
}
#Override
public S get(int index) {
return cache.computeIfAbsent(getSource().get(index), mapper);
}
#Override
public int size() {
return getSource().size();
}
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX TableView use checkboxes to populate list

I'm fairly new to JavaFX and I'm trying to accomplish this principle in JavaFX: I've got a TableView populated with Student objects. I want the first column to be a checkbox with which I can select each row to perform a bulk action on the selected items (as commonly seen in for example mail applications).
I figured I shouldn't add a SimpleBooleanProperty to the Student class since it is only used in the view layer, which is why I thought I could implement it like this: when a checkbox is checked, the student gets added to a List selectedStudents; when it is unchecked, it is removed. Is this a good approach?
This is the code I've got so far (mainly based on copy-pasting from similar solutions):
voornaamKolom.setCellValueFactory(new PropertyValueFactory<Student, String>("name"));
familienaamKolom.setCellValueFactory(new PropertyValueFactory<Student, String>("fname"));
promotorKolom.setCellValueFactory(new PropertyValueFactory<Student, String>("comment"));
selectedKolom.setCellValueFactory(
new Callback<TableColumn.CellDataFeatures<Student, Boolean>, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<Student, Boolean> p) {
return new SimpleBooleanProperty(p.getValue() != null);
}
});
selectedKolom.setCellFactory(
new Callback<TableColumn<Student, Boolean>, TableCell<Student, Boolean>>() {
#Override
public TableCell<Student, Boolean> call(TableColumn<Student, Boolean> p) {
return new CheckBoxCell(studentenTabel);
}
});
studentenTabel.getItems().setAll(getModel().getStudenten());
--
private class CheckBoxCell extends TableCell<Student, Boolean> {
final CheckBox cellCheckBox = new CheckBox();
CheckBoxCell(final TableView tblView) {
cellCheckBox.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
int selectedIndex = getTableRow().getIndex();
if (!cellCheckBox.isSelected()) {
getModel().selectStudent(selectedIndex); // add to selectedStudents
} else {
getModel().deselectStudent(selectedIndex); // remove from selectedStudents
}
}
});
}
//Display button if the row is not empty
#Override
protected void updateItem(Boolean t, boolean empty) {
super.updateItem(t, empty);
if (!empty) {
setGraphic(cellCheckBox);
}
}
}
The main problem with this code is that the checkboxes are not bound to the table rows. E.g. when I select the 2nd item and change the row order by sorting on another value, the 2nd item is still selected even though it represents another object. When new rows are added to the table, some of them get randomly selected too.
I know this code is probably quite dirty, like I said: I'm new to JavaFX. Any help would be appreciated.
Thanks in advance!
The data type for your check box column seems to me it should be Student; i.e. it's a TableColumn<Student, Student>. The reason for this is that you're really presenting a view of the entire object itself: is the student contained in the collection of selected students. Sort of counter-intuitive but it makes it work.
See if this example helps. I don't have the nice separation of the data into a model that your code hints at, but you should be able to factor that in too.
import javafx.application.Application;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class SelectableTable extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> itemTable = new TableView<>();
for (int i=1; i<=40; i++) {
itemTable.getItems().add(new Item("Item "+i));
}
TableColumn<Item, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
TableColumn<Item, Item> selectedCol = new TableColumn<>("Select");
// Collection of items currently selected via checkboxes in the table
// This will be passed to the TableCell implementation.
ObservableSet<Item> selectedItems = FXCollections.observableSet();
selectedCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Item,Item>, ObservableValue<Item>>() {
#Override
public ObservableValue<Item> call(CellDataFeatures<Item, Item> data) {
return new ReadOnlyObjectWrapper<>(data.getValue());
}
});
selectedCol.setCellFactory(new Callback<TableColumn<Item, Item>, TableCell<Item, Item>>() {
#Override
public TableCell<Item, Item> call(
TableColumn<Item, Item> param) {
return new CheckBoxCell(selectedItems);
}
});
itemTable.getColumns().addAll(selectedCol, nameCol);
Button displayButton = new Button("Display selected");
displayButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
for (Item item : selectedItems) {
System.out.println(item.getName());
}
}
});
Button selectAllButton = new Button("Select all");
selectAllButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
selectedItems.addAll(itemTable.getItems());
}
});
Button selectNoneButton = new Button("Select none");
selectNoneButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
selectedItems.clear();
}
});
HBox buttons = new HBox(5);
buttons.getChildren().addAll(selectAllButton, selectNoneButton, displayButton);
BorderPane root = new BorderPane();
root.setCenter(itemTable);
root.setBottom(buttons);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class CheckBoxCell extends TableCell<Item, Item> {
private final ObservableSet<Item> selectedItems ;
private final CheckBox checkBox ;
public CheckBoxCell(ObservableSet<Item> selectedItems) {
this.selectedItems = selectedItems ;
this.checkBox = new CheckBox() ;
// listener to update the set of selected items when the
// check box is checked or unchecked:
checkBox.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
if (checkBox.isSelected()) {
selectedItems.add(getItem());
} else {
selectedItems.remove(getItem());
}
}
});
// listener to update the check box when the collection of selected
// items changes:
selectedItems.addListener(new SetChangeListener<Item>() {
#Override
public void onChanged(Change<? extends Item> change) {
Item item = getItem();
if (item != null) {
checkBox.setSelected(selectedItems.contains(item));
}
}
});
}
#Override
public void updateItem(Item item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
checkBox.setSelected(selectedItems.contains(item));
setGraphic(checkBox);
}
}
}
public static class Item {
private final StringProperty name = new SimpleStringProperty(this, "name");
public StringProperty nameProperty() {
return name ;
}
public final String getName() {
return name.get();
}
public final void setName(String name) {
this.name.set(name);
}
public Item(String name) {
setName(name);
}
}
public static void main(String[] args) {
launch(args);
}
}
You can implement getModel().selectStudent(selectedObject);
and getModel().deselectStudent(selectedObject); instead. In here, selectedObject should be the object itself and not just the index. The model should point to the objects instead of indexes.
It's good to remember that you can make your model point to the exact objects you created. You just have to make sure the correct objects are being pointed to.

ValueChangeEvent not received my Composite Radio Button Group?

When I use two radio buttons with the same name then they are in a group. If one gets selected the other one gets unselected.
I want to build my own Radio Button Widget which is represented by the following code.
How can I achieve that if more than one of my widgets have the same name only one is selected just like for normal radio buttons that are grouped?
public class MyRadioButton extends Composite implements HasText, HasName, HasValueChangeHandlers<Boolean>, HasValue<Boolean> {
private FlowPanel picker;
private boolean isChecked;
public MyRadioButton() {
picker = new FlowPanel();
initWidget(picker);
addValueChangeHandler(new ValueChangeHandler<Boolean>() {
#Override
public void onValueChange(ValueChangeEvent<Boolean> event) {
ValueChangeEvent.fire(MyRadioButton.this, isChecked);
}
});
}
#Override
public void setValue(Boolean value, boolean fireEvents) {
...
if (fireEvents) {
ValueChangeEvent.fire(MyRadioButton.this, value);
}
}
}
Do you solved your problem? If not this could help
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.RadioButton;
public class MyRadioButton extends Composite implements
DeselectOtherRadioButtonsHandler {
private RadioButton rb;
private String radioButtonGroup;
public MyRadioButton(String radioButtonGroup) {
rb = new RadioButton("Foo Button");
getEventBus().addHandler(DeselectOtherRadioButtonsEvent.getType(), this);
rb.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
#Override
public void onValueChange(ValueChangeEvent<Boolean> event) {
if (event.getValue().booleanValue()) {
// deselect other in same group
getEventBus().fireEvent(new DeselectOtherRadioButtonsEvent(radioButtonGroup);
}
}
});
}
#Override
public void onDeselectOtherRadioButtons(DeselectOtherRadioButtonsEvent event) {
// Same RadioButton Group?
if (radioButtonGroup.equalsIgnoreCase(event.getRadioButtonGroup())) {
rb.setValue(false);
}
}
}
class DeselectOtherRadioButtonsEvent extends
GwtEvent<DeselectOtherRadioButtonsHandler> {
private final static Type<DeselectOtherRadioButtonsHandler> TYPE = new Type<DeselectOtherRadioButtonsHandler>();
private final String radioButtonGroup;
public DeselectOtherRadioButtonsEvent(String radioButtonGroup) {
this.radioButtonGroup = radioButtonGroup;
}
public String getRadioButtonGroup() {
return radioButtonGroup;
}
#Override
public Type<DeselectOtherRadioButtonsHandler> getAssociatedType() {
return TYPE;
}
#Override
protected void dispatch(DeselectOtherRadioButtonsHandler handler) {
handler.onDeselectOtherRadioButtons(this);
}
}
interface DeselectOtherRadioButtonsHandler extends EventHandler {
void onDeselectOtherRadioButtons(DeselectOtherRadioButtonsEvent event);
}

Blinking background rows of TableViewer or TreeViewer in SWT

I need the ability to have a blinking (red, maybe more colors) background for rows in a TableViewer/TreeViewer. What are the best options?
There may be more than one row blinking, the blinking MUST be synchron and I need two blinking modes, fast and slow.
I would do something similar to this. Update the elements that you need to change the colors for at a regular interval. At each update toggle the colors depending on how you want them to flash.
void scheduleColorChange(final Color colors[], final int startIndex, final int changeInterval)
{
getDisplay().timerExec(changeInterval, new Runnable()
{
public void run()
{
Object[] elements = getColorChangingElements();
setColorsForFlashingElements(elements, colors[index%colors.length]);
getViewer().update(elements);
scheduleColorChange(colors, startIndex+1, changeInterval)
}
});
}
and the have you label provider implement IColorProvider.
Howdy, this is a fast hack that shows the idea, improvable in many ways. I show the three classes doing the job. If you want I can provide an exported source plugin ready to install into your eclipse workbench tomorrow. Here are the core classes:
import java.util.TimerTask;
import org.eclipse.jface.resource.ColorDescriptor;
import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.ui.PlatformUI;
public class Blinker extends LabelProvider implements ITableLabelProvider, IColorProvider {
private final TableViewer viewer;
public Blinker(TableViewer viewer){
this.viewer = viewer;
}
// this is just a lousy way to store blink_on/blink_off...
private byte state;
// there must be a better way to get a color...
final static Color red = ColorDescriptor.createFrom(new RGB(255,0,0)).createColor(PlatformUI.getWorkbench().getDisplay());
final static Color green = ColorDescriptor.createFrom(new RGB(0,255,0)).createColor(PlatformUI.getWorkbench().getDisplay());
private final TimerTask task = new TimerTask(){
#Override
public void run() {
state++;
PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable(){
public void run() {
viewer.refresh();
}
});
}
};
private Timer t;
synchronized byte getState(){
return state;
}
#Override
public Image getColumnImage(Object element, int columnIndex) {
return null;
}
#Override
public String getColumnText(Object element, int columnIndex) {
return ((Element) element).text;
}
#Override
public Color getBackground(Object object) {
Element element = (Element) object;
if (element.isBlinking()){
return getState() % 2 == 0 ? Blinker.red : Blinker.green;
} else {
return Blinker.green;
}
}
#Override
public Color getForeground(Object element) {
return null;
}
public void start() {
t = new Timer();
t.schedule(task, 200, 1000);
}
public void stop() {
t.cancel();
t = null;
}
}
This is a sample model class. Blink state is stored within the object, but you might want to improve this by using some sort of Adapter:
package com.example.blinker;
public class Element {
private boolean blinking;
public final String text;
public Element(String string, boolean b) {
this.text = string;
this.blinking = b;
}
public synchronized boolean isBlinking(){
return blinking;
}
public synchronized void setBlinking(boolean b){
this.blinking = b;
}
public static final Element[] sampledata = new Element[] {
new Element("Merkur", false),
new Element("Venus", true),
new Element("Erde", false),
new Element("Mars", true),
new Element("Jupiter", false),
new Element("Saturn", true),
new Element("Uranus", false),
new Element("Neptun", true),
new Element("Pluto", false),
};
}
And finally a TableViewer embedded in a View, making use of the two above:
package com.example.blinker.views;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;
import com.example.blinker.Blinker;
import com.example.blinker.Element;
public class SampleView extends ViewPart {
public static final String ID = "com.example.blinker.views.SampleView";
private TableViewer viewer;
private Blinker blinker;
class ViewContentProvider implements IStructuredContentProvider {
public void inputChanged(Viewer v, Object oldInput, Object newInput) {}
public void dispose() {}
public Object[] getElements(Object parent) {
return Element.sampledata;
}
}
public void createPartControl(Composite parent) {
viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
viewer.setContentProvider(new ViewContentProvider());
blinker = new Blinker(viewer);
viewer.setLabelProvider(blinker);
viewer.setInput(new Object());
blinker.start();
}
public void setFocus() {
viewer.getControl().setFocus();
}
public void dispose() {
blinker.stop();
}
}
You should have a construction that resembles something like this:
LinkedList<Row> rows = new LinkedList<Row>();
Thread blinker = null;
public void start() {
blinker = new Thread() {
public void run() {
while(!this.interrupted()) {
try {
synchronized (rows) {
for (Row row : rows) {
row.setForegroundColor(color1);
}
}
Thread.sleep(500);
synchronized (rows) {
for (Row row : rows) {
row.setForegroundColor(color2);
}
}
Thread.sleep(500);
} catch (InterruptException e) {
break;
}
}
}
};
blinker.start();
}
public void stop() {
if (blinker != null) {
blinker.interrupt();
}
}
public void add(Row row) {
synchronized (rows) {
if (!rows.contains(row)) {
rows.add(row);
}
}
}
public void remove(Row row) {
synchronized (rows) {
rows.remove(row);
}
}
When the Shell displays, it should call start(). When it disposes, call stop().
Note that I haven't actually tested this; it's some Javaish pseudocode. If you can't set the row color with the suggested setForegroundColor() above, you could perhaps introduce a widget and define a paint() event.

Categories