SWT: Hyperlink in TableViewerColumn with MouseListener and correct column width - java-8

I want to add a Hyperlink (or a text that is styled like a Hyperlink) in a TableViewerColumn Cell.
I tried this and this.
Summarize: I either get a clickable Hyperlink for which the text is cropped (the column width is too small) or I get a link that is not clickable (MouseListener missing, no cursor).
Both tries are making use of a StyledCellLabelProvider for the TableViewerColumn. The first try does this:
TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
column .getColumn().setText(TITLE);
column .setLabelProvider(new MyHyperlinkLabelProvider());
However, the text in the cell is cropped and I have no idea how to set the column width so that the text fits in the cell. I tried with using pack(), but it had no effect.
private final class MyHyperlinkLabelProvider extends StyledCellLabelProvider {
private MyHyperlinkLabelProvider() {
}
#Override
public void update(ViewerCell cell) {
TableItem item = (TableItem)cell.getItem();
String myText= "Hyperlink text, unfortunately cropped";
link = new MyHyperlink((Composite)cell.getViewerRow().getControl(), SWT.NONE);
toolkit.adapt(link);
link.setText(myText);
TableEditor editor = new TableEditor(item.getParent());
editor.grabHorizontal = true;
editor.grabVertical = true;
editor.setEditor(link, item, cell.getColumnIndex());
GridDataFactory.fillDefaults().applyTo(editor.getEditor());
editor.layout();
link.addMouseListener(new MouseAdapter() {
#Override
public void mouseUp(MouseEvent event) {
super.mouseUp(event);
if (event.getSource() instanceof MyHyperlink) {
MyHyperlink link = (MyHyperlink)event.getSource();
System.out.println("Label was clicked: " + link.getText());
}
}
});
super.update(cell);
}
}
private class MyHyperlink extends Hyperlink {
public MyHyperlink(Composite parent, int style) {
super(parent, style);
this.setUnderlined(true);
}
}
The less preferred alternative is given in the second link. If I cannot get the column width correct, I'd go with this.
The second link suggests to try it with StyledString instead of Hyperlink. The StyledString at least is shown in full width and the column has the correct width. However, you cannot add a MouseListener to a StyledString. Only on the table, but that doesn't help.
private final class MyHyperlinkLabelProvider extends StyledCellLabelProvider {
private MyHyperlinkLabelProvider() {
}
#Override
public void update(ViewerCell cell) {
TableItem item = (TableItem)cell.getItem();
String mytext= "This is the hyperlink text";
/* make text look like a link */
StyledString text = new StyledString();
StyleRange myStyledRange =
new StyleRange(0, phase.length(), Display.getCurrent().getSystemColor(SWT.COLOR_BLUE), null);
myStyledRange.underline = true;
text.append(mytext, StyledString.DECORATIONS_STYLER);
cell.setText(text.toString());
StyleRange[] range = {myStyledRange };
cell.setStyleRanges(range);
super.update(cell);
}
}

To have the column size computed correctly, the cell text has to be set. Although the Hyperlink is what should be displayed link.setText(myText); is not enough. cell.setText(myText) is needed, too.
One can see it in the second snippet, for which the column size is correct. It is because of the cell.setText() call.

Related

Not able to set Redaction Color in iText7 (C#)

I'm not able to change PDF redaction's color in iText7 + PDFSweep using the C# code below. The RED redaction box takes effect only on the first page of the PDF file, then on subsequent pages the color of the redaction box reverts back to BLACK
String input = SRC_FOLDER + "/report.pdf";
String output = SRC_FOLDER + "/report_redacted.pdf";
CompositeCleanupStrategy strategy = new CompositeCleanupStrategy();
strategy.Add(new RegexBasedCleanupStrategy(#"(\d\d\d\d)").SetRedactionColor(ColorConstants.RED));
PdfDocument pdf = new PdfDocument(new PdfReader(input), new PdfWriter(output));
PdfAutoSweep autoSweep = new PdfAutoSweep(strategy);
autoSweep.CleanUp(pdf);
pdf.Close();
It's a bug in pdfSweep.
ìText handles documents on a page by page basis.
In order to be able to re-use the same strategy on different pages, every ICleanupStrategy needs to provide a reset method.
The current implementation for that reset method for RegexBasedCleanupStragegy is
public ICleanupStrategy reset() {
return new RegexBasedCleanupStrategy(this.pattern);
}
Which copies the strategy's pattern, but not its color. As a result, on every page but the first one, the color will default back to black.
To fix this, simply create your own implementation that overrides this behavior to also copy the color.
I will report this as a bug (iText developer here)
for the sake of completion, this would be the fixed approach:
public class RegexBasedCleanupStrategy extends
RegexBasedLocationExtractionStrategy implements ICleanupStrategy {
private Pattern pattern;
private Color redactionColor = ColorConstants.BLACK;
public RegexBasedCleanupStrategy(String regex) {
super(regex);
this.pattern = Pattern.compile(regex);
}
public RegexBasedCleanupStrategy(Pattern pattern) {
super(pattern);
this.pattern = pattern;
}
#Override
public Color getRedactionColor(IPdfTextLocation location) {
return redactionColor;
}
public RegexBasedCleanupStrategy setRedactionColor(Color color) {
this.redactionColor = color;
return this;
}
public ICleanupStrategy reset() {
RegexBasedCleanupStrategy copy = new RegexBasedCleanupStrategy(pattern);
copy.redactionColor = redactionColor;
return copy;
}
}

JavaFX weird (Key)EventBehavior

So I have been experimenting with it a litle bit with javaFX and I came across some rather weird behavior which might be linked to the TableView#edit() method.
I'll post a working example on the bottom of this post again, so you can see what exactually is happening on which cell (debuging included!).
I'll try to explain all the behavior myself, though its way easier to see it for yourself. Basically the events are messed up when using the TableView#edit() method.
1:
If you are using the contextMenu to add a new item, the keyEvents for the the keys 'escape' and 'Enter' (and propably the arrow keys, though I dont use them right now) are consumed before they fire the events on the Cells (e.g. textField and cell KeyEvents!) Though it is firing the keyEvent on the Parent node. (the AnchorPane in this case).
Now I know for a fact that these keys are captured and consumed by the contextMenu default behavior. Though it shouldn't be happening since the contextMenu is already hidden after the new item is added. further more the textField should recieve the events, especially when it is focused!
2:
When you use the button at the bottom of the TableView to add a new Item, The keyEvents are fired on the Parent node (the AnchorPane) and the Cell. Though the textField (even when focused) recieve no keyEvents at all. I cannot explain why the TextField wouldn't recieve any event even when typed in, so I assume that would definitely be a bug?
3:
When editing a cell through double click, it updates the editingCellProperty of the TableView correctly (which I check for several times). Though when start editing though the contextMenu Item (which only calls startEdit() for testpurpose) It doesnt update the editing state correctly! Funny enough it allows the keyEvents to continue as usual, unlike situation 1 & 2.
4:
When you edit an item, and then add an item (either way will cause this problem) it will update the editingCellProperty to the current cell, though when stop editing, it somehow revert back to the last Cell?!? Thats the part where funny things are happening, which I really cannot explain.
Note that the startEdit() & cancelEdit() methods are called in weird moments, and on the wrong Cells!
Right now I dont understand any of this logic. If this is intended behavior, some explanation of it would be greatly appreciated!
This is the example:
package testpacket;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
public class EditStateTest extends Application
{
private static ObservableList<SimpleStringProperty> exampleList = FXCollections.observableArrayList();
//Placeholder for the button
private static SimpleStringProperty PlaceHolder = new SimpleStringProperty();
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception
{
// basic ui setup
AnchorPane parent = new AnchorPane();
Scene scene = new Scene(parent);
primaryStage.setScene(scene);
//fill backinglist with data
for(int i = 0 ; i < 20; i++)
exampleList.add(new SimpleStringProperty("Hello Test"));
exampleList.add(PlaceHolder);
//create a basic tableView
TableView<SimpleStringProperty> listView = new TableView<SimpleStringProperty>();
listView.setEditable(true);
TableColumn<SimpleStringProperty, String> column = new TableColumn<SimpleStringProperty, String>();
column.setCellFactory(E -> new TableCellTest<SimpleStringProperty, String>());
column.setCellValueFactory(E -> E.getValue());
column.setEditable(true);
// set listViews' backing list
listView.setItems(exampleList);
listView.getColumns().clear();
listView.getColumns().add(column);
parent.getChildren().add(listView);
parent.setOnKeyReleased(E -> System.out.println("Parent - KeyEvent"));
primaryStage.show();
}
// basic editable cell example
public static class TableCellTest<S, T> extends TableCell<S, T>
{
// The editing textField.
protected static Button addButton = new Button("Add");
protected TextField textField = new TextField();;
protected ContextMenu menu;
public TableCellTest()
{
this.setOnContextMenuRequested(E -> {
if(this.getTableView().editingCellProperty().get() == null)
this.menu.show(this, E.getScreenX(), E.getScreenY());
});
this.menu = new ContextMenu();
MenuItem createNew = new MenuItem("create New");
createNew.setOnAction(E -> {
System.out.println("Cell ContextMenu " + this.getIndex() + " - createNew: onAction");
this.onNewItem(this.getIndex() + 1);
});
MenuItem edit = new MenuItem("edit");
edit.setOnAction(E -> {
System.out.println("Cell ContextMenu " + this.getIndex() + " - edit: onAction");
this.startEdit();
});
this.menu.getItems().setAll(createNew, edit);
addButton.addEventHandler(ActionEvent.ACTION, E -> {
if(this.getIndex() == EditStateTest.exampleList.size() - 1)
{
System.out.println("Cell " + this.getIndex() + " - Button: onAction");
this.onNewItem(this.getIndex());
}
});
addButton.prefWidthProperty().bind(this.widthProperty());
this.setOnKeyReleased(E -> System.out.println("Cell " + this.getIndex() + " - KeyEvent"));
}
public void onNewItem(int index)
{
EditStateTest.exampleList.add(index, new SimpleStringProperty("New Item"));
this.getTableView().edit(index, this.getTableColumn());
textField.requestFocus();
}
#Override
public void startEdit()
{
if (!isEditable()
|| (this.getTableView() != null && !this.getTableView().isEditable())
|| (this.getTableColumn() != null && !this.getTableColumn().isEditable()))
return;
System.out.println("Cell " + this.getIndex() + " - StartEdit");
super.startEdit();
this.createTextField();
textField.setText((String)this.getItem());
this.setGraphic(textField);
textField.selectAll();
this.setText(null);
}
#Override
public void cancelEdit()
{
if (!this.isEditing())
return;
System.out.println("Cell " + this.getIndex() + " - CancelEdit");
super.cancelEdit();
this.setText((String)this.getItem());
this.setGraphic(null);
}
#Override
protected void updateItem(T item, boolean empty)
{
System.out.println("Cell " + this.getIndex() + " - UpdateItem");
super.updateItem(item, empty);
if(empty || item == null)
{
if(this.getIndex() == EditStateTest.exampleList.size() - 1)
{
this.setText("");
this.setGraphic(addButton);
}
else
{
this.setText(null);
this.setGraphic(null);
}
}
else
{
// These checks are needed to make sure this cell is the specific cell that is in editing mode.
// Technically this#isEditing() can be left out, as it is not accurate enough at this point.
if(this.getTableView().getEditingCell() != null
&& this.getTableView().getEditingCell().getRow() == this.getIndex())
{
//change to TextField
this.setText(null);
this.setGraphic(textField);
}
else
{
//change to actual value
this.setText((String)this.getItem());
this.setGraphic(null);
}
}
}
#SuppressWarnings("unchecked")
public void createTextField()
{
textField.setOnKeyReleased(E -> {
System.out.println("TextField " + this.getIndex() + " - KeyEvent");
System.out.println(this.getTableView().getEditingCell());
// if(this.getTableView().getEditingCell().getRow() == this.getIndex())
if(E.getCode() == KeyCode.ENTER)
{
this.setItem((T) textField.getText());
this.commitEdit(this.getItem());
}
else if(E.getCode() == KeyCode.ESCAPE)
this.cancelEdit();
});
}
}
}
I hope somebody could help me further with this. If you have suggestions/solutions or workarounds for this, please let me know!
Thanks for your time!
This is kind of the poster child for Josh Bloch's "Inheritance breaks Encapsulation" mantra. What I mean by that is that when you create a subclass of an existing class (TableCell in this case), you need to know a lot about the implementation of that class in order to make the subclass play nicely with the superclass. You make a lot of assumptions in your code about the interaction between the TableView and its cells that are not true, and that (along with some bugs and general weird implementations of event handling in some controls) is why your code is breaking.
I don't think I can address every single issue, but I can give some general pointers here and provide what I think is working code that achieves what you are trying to achieve.
First, cells are reused. This is a good thing, because it makes the table perform very efficiently when there is a large amount of data, but it makes it complicated. The basic idea is that cells are essentially only created for the visible items in the table. As the user scrolls around, or as the table content changes, cells that are no longer needed are reused for different items that become visible. This massively saves on memory consumption and CPU time (if used properly). In order to be able to improve the implementation, the JavaFX team deliberately don't specify how this works, and how and when cells are likely to be reused. So you have to be careful about making assumptions about the continuity of the item or index fields of a cell (and conversely, which cell is assigned to a given item or index), particularly if you change the structure of the table.
What you are basically guaranteed is:
Any time the cell is reused for a different item, the updateItem() method is invoked before the cell is rendered.
Any time the index of the cell changes (which may be because an item is inserted in the list, or may be because the cell is reused, or both), the updateIndex() method is invoked before the cell is rendered.
However, note that in the case where both change, there is no guarantee of the order in which these are invoked. So, if your cell rendering depends on both the item and the index (which is the case here: you check both the item and the index in your updateItem(...) method), you need to ensure the cell is updated when either of those properties change. The best way (imo) to achieve this is to create a private method to perform the update, and to delegate to it from both updateItem() and updateIndex(). This way, when the second of those is invoked, your update method is invoked with consistent state.
If you change the structure of the table, say by adding a new row, the cells will need to be rearranged, and some of them are likely to be reused for different items (and indexes). However, this rearrangement only happens when the table is laid out, which by default will not happen until the next frame rendering. (This makes sense from a performance perspective: imagine you make 1000 different changes to a table in a loop; you don't want the cells to be recalculated on every change, you just want them recalculated once the next time the table is rendered to the screen.) This means, if you add rows to the table, you cannot rely on the index or item of any cell being correct. This is why your call to table.edit(...) immediately after adding a new row is so unpredictable. The trick here is to force a layout of the table by calling TableView.layout() after adding the row.
Note that pressing "Enter" when a table cell is focused will cause that cell to go into editing mode. If you handle commits on the text field in a cell with a key released event handler, these handlers will interact in an unpredictable way. I think this is why you see the strange key handling effects you see (also note that text fields consume the key events they process internally). The workaround for that is to use an onAction handler on the text field (which is arguably more semantic anyway).
Don't make the button static (I have no idea why you would want to do this anyway). "Static" means that the button is a property of the class as a whole, not of the instances of that class. So in this case, all the cells share a reference to a single button. Since the cell reuse mechanism is unspecified, you don't know that only one cell will have the button set as its graphic. This can cause disaster. For example, if you scroll the cell with the button out of view and then back into view, there is no guarantee the same cell will be used to display that last item when it comes back into view. It is possible (I don't know the implementation) that the cell that previously displayed the last item is sitting unused (perhaps part of the virtual flow container, but clipped out of view) and is not updated. In that case, the button would then appear twice in the scene graph, which would either throw an exception or cause unpredictable behavior. There's basically no valid reason to ever make a scene graph node static, and here it's a particularly bad idea.
To code functionality like this, you should read extensively the documentation for the cell mechanism and for TableView, TableColumn, and TableCell. At some point you might find you need to dig into the source code to see how the provided cell implementations work.
Here's (I think, I'm not sure I've fully tested) a working version of what I think you were looking for. I made some slight changes to the structure (no need for StringPropertys as the data type, String works just fine as long as you have no identical duplicates), added an onEditCommit handler, etc.
import javafx.application.Application;
import javafx.beans.value.ObservableValueBase;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableViewWithAddAtEnd extends Application {
#Override
public void start(Stage primaryStage) {
TableView<String> table = new TableView<>();
table.setEditable(true);
TableColumn<String, String> column = new TableColumn<>("Data");
column.setPrefWidth(150);
table.getColumns().add(column);
// use trivial wrapper for string data:
column.setCellValueFactory(cellData -> new ObservableValueBase<String>() {
#Override
public String getValue() {
return cellData.getValue();
}
});
column.setCellFactory(col -> new EditingCellWithMenuEtc());
column.setOnEditCommit(e ->
table.getItems().set(e.getTablePosition().getRow(), e.getNewValue()));
for (int i = 1 ; i <= 20; i++) {
table.getItems().add("Item "+i);
}
// blank for "add" button:
table.getItems().add("");
BorderPane root = new BorderPane(table);
primaryStage.setScene(new Scene(root, 600, 600));
primaryStage.show();
}
public static class EditingCellWithMenuEtc extends TableCell<String, String> {
private TextField textField ;
private Button button ;
private ContextMenu contextMenu ;
// The update relies on knowing both the item and the index
// Since we don't know (or at least shouldn't rely on) the order
// in which the item and index are updated, we just delegate
// implementations of both updateItem and updateIndex to a general
// method. This way doUpdate() is always called last with consistent
// state, so we are guaranteed to be in a consistent state when the
// cell is rendered, even if we are temporarily in an inconsistent
// state between the calls to updateItem and updateIndex.
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
doUpdate(item, getIndex(), empty);
}
#Override
public void updateIndex(int index) {
super.updateIndex(index);
doUpdate(getItem(), index, isEmpty());
}
// update the cell. This updates the text, graphic, context menu
// (empty cells and the special button cell don't have context menus)
// and editable state (empty cells and the special button cell can't
// be edited)
private void doUpdate(String item, int index, boolean empty) {
if (empty) {
setText(null);
setGraphic(null);
setContextMenu(null);
setEditable(false);
} else {
if (index == getTableView().getItems().size() - 1) {
setText(null);
setGraphic(getButton());
setContextMenu(null);
setEditable(false);
} else if (isEditing()) {
setText(null);
getTextField().setText(item);
setGraphic(getTextField());
getTextField().requestFocus();
setContextMenu(null);
setEditable(true);
} else {
setText(item);
setGraphic(null);
setContextMenu(getMenu());
setEditable(true);
}
}
}
#Override
public void startEdit() {
if (! isEditable()
|| ! getTableColumn().isEditable()
|| ! getTableView().isEditable()) {
return ;
}
super.startEdit();
getTextField().setText(getItem());
setText(null);
setGraphic(getTextField());
setContextMenu(null);
textField.selectAll();
textField.requestFocus();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText(getItem());
setGraphic(null);
setContextMenu(getMenu());
}
#Override
public void commitEdit(String newValue) {
// note this fires onEditCommit handler on column:
super.commitEdit(newValue);
setText(getItem());
setGraphic(null);
setContextMenu(getMenu());
}
private void addNewItem(int index) {
getTableView().getItems().add(index, "New Item");
// force recomputation of cells:
getTableView().layout();
// start edit:
getTableView().edit(index, getTableColumn());
}
private ContextMenu getMenu() {
if (contextMenu == null) {
createContextMenu();
}
return contextMenu ;
}
private void createContextMenu() {
MenuItem addNew = new MenuItem("Add new");
addNew.setOnAction(e -> addNewItem(getIndex() + 1));
MenuItem edit = new MenuItem("Edit");
// note we call TableView.edit(), not this.startEdit() to ensure
// table's editing state is kept consistent:
edit.setOnAction(e -> getTableView().edit(getIndex(), getTableColumn()));
contextMenu = new ContextMenu(addNew, edit);
}
private Button getButton() {
if (button == null) {
createButton();
}
return button ;
}
private void createButton() {
button = new Button("Add");
button.prefWidthProperty().bind(widthProperty());
button.setOnAction(e -> addNewItem(getTableView().getItems().size() - 1));
}
private TextField getTextField() {
if (textField == null) {
createTextField();
}
return textField ;
}
private void createTextField() {
textField = new TextField();
// use setOnAction for enter, to avoid conflict with enter on cell:
textField.setOnAction(e -> commitEdit(textField.getText()));
// use key released for escape: note text fields do note consume
// key releases they don't handle:
textField.setOnKeyReleased(e -> {
if (e.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
});
}
}
public static void main(String[] args) {
launch(args);
}
}
My big learn item of the day (freely summarized and slightly extended from James' answer):
view.edit(...) is safe to call only if all cells are in a stable state and the target cell is visible. Most of the time we can force the stable state by calling view.layout()
Below is yet another example to play with:
as already mentioned in one of my comments, it differs from James' in starting the edit in a listener to the items: might not always be the best place, has the advantage of a single location (at least as far as list mutations are involved) for the layout call. A drawback is that we need to be certain that the viewSkin's listener to the items is called before ours. To guarantee that, our own listener is re/registered whenever the skin changes.
as an exercise in re-use, I extended TextFieldTableCell to additionally handle the button/menu and update the cell's editability based on the row item.
there are also buttons outside the table to experiment with: addAndEdit and scrollAndEdit. The latter is to demonstrate that "instable cell state" can be reached by paths different from modifying the items.
Currently, I tend to subclass TableView and override its edit(...) to force the re-layout. Something like:
public static class TTableView<S> extends TableView<S> {
/**
* Overridden to force a layout before calling super.
*/
#Override
public void edit(int row, TableColumn<S, ?> column) {
layout();
super.edit(row, column);
}
}
Doing, relieves the burden on client code. What's left for them is to make sure the target cell is scrolled into the visible area, though.
The example:
public class TablePersonAddRowAndEdit extends Application {
private PersonStandIn standIn = new PersonStandIn();
private final ObservableList<Person> data =
// Person from Tutorial - with Properties exposed!
FXCollections.observableArrayList(
new Person("Jacob", "Smith", "jacob.smith#example.com"),
new Person("Isabella", "Johnson", "isabella.johnson#example.com"),
new Person("Ethan", "Williams", "ethan.williams#example.com"),
new Person("Emma", "Jones", "emma.jones#example.com"),
new Person("Michael", "Brown", "michael.brown#example.com")
, standIn
);
private Parent getContent() {
TableView<Person> table = new TableView<>();
table.setItems(data);
table.setEditable(true);
TableColumn<Person, String> firstName = new TableColumn<>("First Name");
firstName.setCellValueFactory(new PropertyValueFactory<>("firstName"));
firstName.setCellFactory(v -> new MyTextFieldCell<>());
ListChangeListener l = c -> {
while (c.next()) {
// true added only
if (c.wasAdded() && ! c.wasRemoved()) {
// force the re-layout before starting the edit
table.layout();
table.edit(c.getFrom(), firstName);
return;
}
};
};
// install the listener to the items after the skin has registered
// its own
ChangeListener skinListener = (src, ov, nv) -> {
table.getItems().removeListener(l);
table.getItems().addListener(l);
};
table.skinProperty().addListener(skinListener);
table.getColumns().addAll(firstName);
Button add = new Button("AddAndEdit");
add.setOnAction(e -> {
int standInIndex = table.getItems().indexOf(standIn);
int index = standInIndex < 0 ? table.getItems().size() : standInIndex;
index =1;
Person person = createNewItem("edit", index);
table.getItems().add(index, person);
});
Button edit = new Button("Edit");
edit.setOnAction(e -> {
int index = 1;//table.getItems().size() -2;
table.scrollTo(index);
table.requestFocus();
table.edit(index, firstName);
});
HBox buttons = new HBox(10, add, edit);
BorderPane content = new BorderPane(table);
content.setBottom(buttons);
return content;
}
/**
* A cell that can handle not-editable items. Has to update its
* editability based on the rowItem. Must be done in updateItem
* (tried a listener to the tableRow's item, wasn't good enough - doesn't
* get notified reliably)
*
*/
public static class MyTextFieldCell<S> extends TextFieldTableCell<S, String> {
private Button button;
public MyTextFieldCell() {
super(new DefaultStringConverter());
ContextMenu menu = new ContextMenu();
menu.getItems().add(createMenuItem());
setContextMenu(menu);
}
private boolean isStandIn() {
return getTableRow() != null && getTableRow().getItem() instanceof StandIn;
}
/**
* Update cell's editable based on the rowItem.
*/
private void doUpdateEditable() {
if (isEmpty() || isStandIn()) {
setEditable(false);
} else {
setEditable(true);
}
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
doUpdateEditable();
if (isStandIn()) {
if (isEditing()) {
LOG.info("shouldn't be editing - has StandIn");
}
if (button == null) {
button = createButton();
}
setText(null);
setGraphic(button);
}
}
private Button createButton() {
Button b = new Button("Add");
b.setOnAction(e -> {
int index = getTableView().getItems().size() -1;
getTableView().getItems().add(index, createNewItem("button", index));
});
return b;
}
private MenuItem createMenuItem() {
MenuItem item = new MenuItem("Add");
item.setOnAction(e -> {
if (isStandIn()) return;
int index = getIndex();
getTableView().getItems().add(index, createNewItem("menu", index));
});
return item;
}
private S createNewItem(String text, int index) {
return (S) new Person(text + index, text + index, text);
}
}
private Person createNewItem(String text, int index) {
return new Person(text + index, text + index, text);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent()));
primaryStage.setTitle(FXUtils.version());
primaryStage.show();
}
/**
* Marker-Interface to denote a class as not mutable.
*/
public static interface StandIn {
}
public static class PersonStandIn extends Person implements StandIn{
public PersonStandIn() {
super("standIn", "", "");
}
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TablePersonAddRowAndEdit.class.getName());
}
Update
shouldn't have been too surprised - a related problem was discussed half a year ago (and produced a bug report)

How to unmask a JavaFX PasswordField or properly mask a TextField?

In a UI of mine, I have a PasswordField like so (urm the one at the bottom!):
I want a user to be able to check the checkbox you see in the picture and have all "secret" password characters displayed. Not much different from the option we get from many modern password-asking UI:s floating around. However, I cannot find anything in the JavaFX API that let me do that?
If my worries hold true, then I would like to use a TextField that display the last key pressed for only half a second or until next key is pressed, and then he shall mask all previous user input. This would produce a cool animation effect that one can see sometimes in modern UI:s. However, is there a way for me to get hold of the OS dependent (I think it is OS dependent??) password echo character I should use?
If it is not possible to get that OS dependent character, then I'd be glad to use the character you see on the picture (JavaFX on a Windows 8 machine). What is the UTF-8 code point for this stranger?
> However, I cannot find anything in the JavaFX API that let me do that?
The PasswordField component does not display masked text by default. However you can use PasswordField with TextField and toggle masked/unmasked text using these components respectively. Where the unmasked text is shown by TextField, as in example demo below.
> I would like to use a TextField that display the last key pressed for only half a second or until next key is pressed, and then he shall mask all previous user input.
Since PasswordField, itself is a extended version of TextField. You can always build your own custom password textbox with properties you mentioned.
> is there a way for me to get hold of the OS dependent (I think it is OS dependent??) password echo character I should use?
Frankly did not grab what you are saying here. You can track text changes by adding change listener to PasswordField.textPrperty() and do animations, timers etc. You can override the default bullet mask by extending PasswordFieldSkin and using it through CSS -fx-skin. See the definition of bullet in its source here:
public class PasswordFieldSkin extends TextFieldSkin {
public static final char BULLET = '\u2022';
public PasswordFieldSkin(PasswordField passwordField) {
super(passwordField, new PasswordFieldBehavior(passwordField));
}
#Override protected String maskText(String txt) {
TextField textField = getSkinnable();
int n = textField.getLength();
StringBuilder passwordBuilder = new StringBuilder(n);
for (int i=0; i<n; i++) {
passwordBuilder.append(BULLET);
}
return passwordBuilder.toString();
}
}
Finally, Here is kick off demo app of showing password characters using bindings:
#Override
public void start(Stage primaryStage) {
// text field to show password as unmasked
final TextField textField = new TextField();
// Set initial state
textField.setManaged(false);
textField.setVisible(false);
// Actual password field
final PasswordField passwordField = new PasswordField();
CheckBox checkBox = new CheckBox("Show/Hide password");
// Bind properties. Toggle textField and passwordField
// visibility and managability properties mutually when checkbox's state is changed.
// Because we want to display only one component (textField or passwordField)
// on the scene at a time.
textField.managedProperty().bind(checkBox.selectedProperty());
textField.visibleProperty().bind(checkBox.selectedProperty());
passwordField.managedProperty().bind(checkBox.selectedProperty().not());
passwordField.visibleProperty().bind(checkBox.selectedProperty().not());
// Bind the textField and passwordField text values bidirectionally.
textField.textProperty().bindBidirectional(passwordField.textProperty());
VBox root = new VBox(10);
root.getChildren().addAll(passwordField, textField, checkBox);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Demo");
primaryStage.setScene(scene);
primaryStage.show();
}
You need create three elements:
TextField : the password visible field
PasswodField : the password not visible field
CheckBox : the toggle visibility field
You place the passwords fields in the same position(x, y):
<PasswordField fx:id="pass_hidden" layoutX="X" layoutY="Y" />
<TextField fx:id="pass_text" layoutX="X" layoutY="Y"/>
<CheckBox fx:id="pass_toggle" onAction="#togglevisiblePassword" .... />
Note: Replaces the value of X and Y.
Add in your controller:
#FXML
private TextField pass_text;
#FXML
private CheckBox pass_toggle;
#FXML
private Button btn_start_stop;
/**
* Controls the visibility of the Password field
* #param event
*/
#FXML
public void togglevisiblePassword(ActionEvent event) {
if (pass_toggle.isSelected()) {
pass_text.setText(pass_hidden.getText());
pass_text.setVisible(true);
pass_hidden.setVisible(false);
return;
}
pass_hidden.setText(pass_text.getText());
pass_hidden.setVisible(true);
pass_text.setVisible(false);
}
//Run
#Override
public void initialize(URL location, ResourceBundle resources) {
this.togglevisiblePassword(null);
}
If you want to know the value of the password you can create a method that returns it:
private String passwordValue() {
return pass_toggle.isSelected()?
pass_text.getText(): pass_hidden.getText();
}
I know this is older, but i was searching for answer and this is my solution:
#FXML
private JFXButton showpassword;
private String password;
showpassword.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
password = passwordField.getText();
passwordField.clear();
passwordField.setPromptText(password);
});
showpassword.addEventFilter(MouseEvent.MOUSE_RELEASED, e -> {
passwordField.setText(password);
passwordField.setPromptText("Password");
});
Using button with graphic like "WIN10 Eye - unmask password"
You could use a custom Tooltip to show the password, and use the Checkbox to show / hide the Tooltip.
The code for this demo can be found here.
void viewpass(ActionEvent event) {
if (checkpass.isSelected()){
pass.setPromptText(pass.getText());
pass.setText("");
pass.setDisable(true);
}else {
pass .setText(pass.getPromptText());
pass.setPromptText("");
pass.setDisable(false);
}
}
You can also do it using textfield and password field with radio button As follows.
import javafx.fxml.Initializable;
import com.jfoenix.controls.*;
import com.jfoenix.controls.JFXPasswordField;
import com.jfoenix.controls.JFXRadioButton;
import javafx.fxml.FXML;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller implements Initializable{
#FXML
private JFXPasswordField PasswordField;
#FXML
private JFXRadioButton passVisible;
#FXML
private JFXTextField textField1;
#Override
public void initialize(URL location, ResourceBundle resources)
{
textField1.textProperty().bind(PasswordField.textProperty());
textField1.visibleProperty().bind(passVisible.selectedProperty());
PasswordField.visibleProperty().bind(passVisible.selectedProperty().not());
}
}
well, the password field has one property that can be set the text in bullets.. this method maskText(String txt) stays on skin.. you can replace this with a new Skin.. when you type the method maskText test if you can raplace in bullets.. use one boolean to inform.. you can reuse this code from another event. it's an example. Regards
public class Main extends Application {
#Override
public void start(Stage stage) throws Exception {
StackPane root = new StackPane();
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(50));
PasswordField passwordField = new PasswordField();
passwordField.setSkin(new VisiblePasswordFieldSkin(passwordField));
root.getChildren().add(passwordField);
stage.setScene(new Scene(root, 400, 400));
stage.show();
}
}
class VisiblePasswordFieldSkin extends TextFieldSkin {
private final Button actionButton = new Button("View");
private final SVGPath actionIcon = new SVGPath();
private boolean mask = true;
public VisiblePasswordFieldSkin(PasswordField textField) {
super(textField);
actionButton.setId("actionButton");
actionButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
actionButton.setPrefSize(30,30);
actionButton.setFocusTraversable(false);
actionButton.setBackground(new Background(new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, new Insets(0))));
getChildren().add(actionButton);
actionButton.setCursor(Cursor.HAND);
actionButton.toFront();
actionIcon.setContent(Icons.VIEWER.getContent());
actionButton.setGraphic(actionIcon);
actionButton.setVisible(false);
actionButton.setOnMouseClicked(event -> {
if(mask) {
actionIcon.setContent(Icons.VIEWER_OFF.getContent());
mask = false;
} else {
actionIcon.setContent(Icons.VIEWER.getContent());
mask = true;
}
textField.setText(textField.getText());
textField.end();
});
textField.textProperty().addListener((observable, oldValue, newValue) -> actionButton.setVisible(!newValue.isEmpty()));
}
#Override
protected void layoutChildren(double x, double y, double w, double h) {
super.layoutChildren(x, y, w, h);
layoutInArea(actionButton, x, y, w, h,0, HPos.RIGHT, VPos.CENTER);
}
#Override
protected String maskText(String txt) {
if (getSkinnable() instanceof PasswordField && mask) {
int n = txt.length();
StringBuilder passwordBuilder = new StringBuilder(n);
for (int i = 0; i < n; i++) {
passwordBuilder.append(BULLET);
}
return passwordBuilder.toString();
} else {
return txt;
}
}
}
enum Icons {
VIEWER_OFF("M12 6c3.79 0 7.17 2.13 8.82 5.5-.59 1.22-1.42 2.27-2." +
"41 3.12l1.41 1.41c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 " +
"0-2.49.2-3.64.57l1.65 1.65C10.66 6.09 11.32 6 12 6zm-1.07 1.14L13 9.21c.57.25 1.03.71 " +
"1.28 1.28l2.07 2.07c.08-.34.14-.7.14-1.07C16.5 9.01 14.48 7 12 7c-.37 0-.72.05-1.07." +
"14zM2.01 3.87l2.68 2.68C3.06 7.83 1.77 9.53 1 11.5 2.73 15.89 7 19 12 19c1.52 0 2.98-.29 " +
"4.32-.82l3.42 3.42 1.41-1.41L3.42 2.45 2.01 3.87zm7.5 7.5l2.61 2.61c-.04.01-.08.02-.12.02-1.38 " +
"0-2.5-1.12-2.5-2.5 0-.05.01-.08.01-.13zm-3.4-3.4l1.75 1.75c-.23.55-.36 1.15-.36 1.78 0 2.48 2.02 " +
"4.5 4.5 4.5.63 0 1.23-.13 1.77-.36l.98.98c-.88.24-1.8.38-2.75.38-3.79 0-7.17-2.13-8.82-5.5.7-1.43 1.72-2.61 2.93-3.53z"),
VIEWER("M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7." +
"5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z");
private String content;
Icons(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
View in GitHub

Javafx textfield resize to text length?

Hello guys I am building a chat server where I use a textfield on the screen to type in the chat message that the user writes, the idea is that it works like a bubble over a persons head when he types a message.
my question is in order to not make a textbox that is too large or too small is there a way to make the textbox resize (trim if you will) so it adjust to the text written in the textfield?
P.S. I'm using JavaFx scenebuilder to do all of this.
You can use computeTextWidth method in the com.sun.javafx.scene.control.skin.Utils. the method is used in javafx.scene.control.Label class to calculate the minimum width for label content.
I solved my problem as below:
field.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> ob, String o,
String n) {
// expand the textfield
field.setPrefWidth(TextUtils.computeTextWidth(field.getFont(),
field.getText(), 0.0D) + 10);
}
});
I have added a listener to textProperty, and with every text change i change the prefWidth of textfield.
Note: as long as the Utils.computeTextWidth() is not public, I have copied the source code to a new class (TextUtils).
Here is the full source code:
package me.jone30rw.fxcontrol;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextBoundsType;
public class TextUtils {
static final Text helper;
static final double DEFAULT_WRAPPING_WIDTH;
static final double DEFAULT_LINE_SPACING;
static final String DEFAULT_TEXT;
static final TextBoundsType DEFAULT_BOUNDS_TYPE;
static {
helper = new Text();
DEFAULT_WRAPPING_WIDTH = helper.getWrappingWidth();
DEFAULT_LINE_SPACING = helper.getLineSpacing();
DEFAULT_TEXT = helper.getText();
DEFAULT_BOUNDS_TYPE = helper.getBoundsType();
}
public static double computeTextWidth(Font font, String text, double help0) {
// Toolkit.getToolkit().getFontLoader().computeStringWidth(field.getText(),
// field.getFont());
helper.setText(text);
helper.setFont(font);
helper.setWrappingWidth(0.0D);
helper.setLineSpacing(0.0D);
double d = Math.min(helper.prefWidth(-1.0D), help0);
helper.setWrappingWidth((int) Math.ceil(d));
d = Math.ceil(helper.getLayoutBounds().getWidth());
helper.setWrappingWidth(DEFAULT_WRAPPING_WIDTH);
helper.setLineSpacing(DEFAULT_LINE_SPACING);
helper.setText(DEFAULT_TEXT);
return d;
}
}
In JavaFX 8, there is a solution for that, here is the code:
TextField tf = new TextField();
// Set Max and Min Width to PREF_SIZE so that the TextField is always PREF
tf.setMinWidth(Region.USE_PREF_SIZE);
tf.setMaxWidth(Region.USE_PREF_SIZE);
tf.textProperty().addListener((ov, prevText, currText) -> {
// Do this in a Platform.runLater because of Textfield has no padding at first time and so on
Platform.runLater(() -> {
Text text = new Text(currText);
text.setFont(tf.getFont()); // Set the same font, so the size is the same
double width = text.getLayoutBounds().getWidth() // This big is the Text in the TextField
+ tf.getPadding().getLeft() + tf.getPadding().getRight() // Add the padding of the TextField
+ 2d; // Add some spacing
tf.setPrefWidth(width); // Set the width
tf.positionCaret(tf.getCaretPosition()); // If you remove this line, it flashes a little bit
});
});
tf.setText("Hello World!");
In JavaFX 2.2 this code works with little limitations. You can't set the Font(so if you do not use the std-font, you must set it manually).
You can't get the padding from a TextField(so if you know the padding, write it hardcoded).
Happy Coding,
Kalasch
Since JavaFX 8, this is by far the simplest:
textField.prefColumnCountProperty().bind(textField.textProperty().length());
It is time to do some coding behind the scenes(builder) :).
The following code chunk is not a neat solution but better than none. :)
// define width limits
textField.setMinWidth(50);
textField.setPrefWidth(50);
textField.setMaxWidth(400);
// add listner
textField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
textField.setPrefWidth(textField.getText().length() * 7); // why 7? Totally trial number.
}
});
No font dependent magic required if you use setPrefColumnCount
tf.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> ob, String o, String n) {
tf.setPrefColumnCount(tf.getText().length() +1);
}
});
The best / easiest way to do this is to use JavaFX's "USE_COMPUTED_SIZE" option. You can either define it in the FXML, or programatically like this:
TextField textField = new TextField("hello");
textField.setPrefWidth(Control.USE_COMPUTED_SIZE);

Adding custom metadata tags using LibTiff.Net

I now how to add a custom tag to an image but it's not showing up as the tag name in image viewer. I only see the number I assigned and its value.
Why there is no proper name for my custom tag?
using BitMiracle.LibTiff.Classic;
namespace WindowsFormsApplication1
{
class Program
{
private const TiffTag IMG_GUID = (TiffTag)666;
private static Tiff.TiffExtendProc m_parentExtender;
public static void TagExtender(Tiff tif)
{
TiffFieldInfo[] tiffFieldInfo =
{
new TiffFieldInfo(IMG_GUID, -1, -1, TiffType.ASCII, FieldBit.Custom, true, false, "IMG_GUID"),
};
tif.MergeFieldInfo(tiffFieldInfo, tiffFieldInfo.Length);
if (m_parentExtender != null)
m_parentExtender(tif);
}
static void Main(string[] args)
{
// Register the extender callback
// It's a good idea to keep track of the previous tag extender (if any) so that we can call it
// from our extender allowing a chain of customizations to take effect.
m_parentExtender = Tiff.SetTagExtender(TagExtender);
byte[] buffer = new byte[25 * 144];
string outputFileName = writeTiffWithCustomTags(buffer);
// restore previous tag extender
Tiff.SetTagExtender(m_parentExtender);
}
private static string writeTiffWithCustomTags(byte[] buffer)
{
string existingTiffName = "..\\..\\tifimages\\cramps.tif";
string outputFileName = existingTiffName;
using (Tiff image = Tiff.Open(outputFileName, "a"))
{
// set custom tags
image.SetDirectory(0);
string value = "test";
image.SetField(IMG_GUID, value);
image.CheckpointDirectory();
// Write the information to the file
image.WriteEncodedStrip(0, buffer, 25 * 144);
}
return outputFileName;
}
}
}
The application you use for viewing your TIFFs should know about your custom tags beforehand in order to be able to display its names.
It's not gonna happen! (Because you may select almost arbitrary integer for your custom tag).
So, there is nothing wrong with custom tags being displayed as (an integer, a value) pair. It's just the way custom tag work.

Resources