How is it possible to filter Nodes in a JavaFX 2 TreeView?
I have a TextField and I want to filter all Nodes (for example node labels) based on the content of the TextField.
Thanks.
this is reusable filterable tree item class i've wrote.
the filter should be bound on predicateProperty, and you must use getSourceChildren method to manipulate tree items.
public class FilterableTreeItem<T> extends TreeItem<T> {
private final ObservableList<TreeItem<T>> sourceChildren = FXCollections.observableArrayList();
private final FilteredList<TreeItem<T>> filteredChildren = new FilteredList<>(sourceChildren);
private final ObjectProperty<Predicate<T>> predicate = new SimpleObjectProperty<>();
public FilterableTreeItem(T value) {
super(value);
filteredChildren.predicateProperty().bind(Bindings.createObjectBinding(() -> {
Predicate<TreeItem<T>> p = child -> {
if (child instanceof FilterableTreeItem) {
((FilterableTreeItem<T>) child).predicateProperty().set(predicate.get());
}
if (predicate.get() == null || !child.getChildren().isEmpty()) {
return true;
}
return predicate.get().test(child.getValue());
};
return p;
} , predicate));
filteredChildren.addListener((ListChangeListener<TreeItem<T>>) c -> {
while (c.next()) {
getChildren().removeAll(c.getRemoved());
getChildren().addAll(c.getAddedSubList());
}
});
}
public ObservableList<TreeItem<T>> getSourceChildren() {
return sourceChildren;
}
public ObjectProperty<Predicate<T>> predicateProperty() {
return predicate;
}
}
There is no special filter, provided by JFX.
So you should implement it by yourself.
The only support from JFX you have - tracking of collection of TreeItems' items. When you add or remove an item, it will be added or removed. But adding or removing from collections you implement yourself.
Related
My aim is to create a custom control displaying some images, which can be added/exchanged by the user of that control. So, if it is added to a Form, the GUI designer should be able to change some or all images provided by the control editing the appropriate attribute.
In my Test-Project I have a simple control with 4 Attributes:
public Image MyImage { get; set; } = null;
public List<int> MyListOfInt { get; set; } = new List<int>();
public List<Image> MyListOfImages { get; set; } = new List<Image>();
public ImageList MyImageList { get; set; } = new ImageList();
Using this control in a Windows Form Project, clicking on
MyImage brings up the 'Select resource' dialog. OK
MyListOfInt brings up the 'Int32 Collection Editor' dialog. OK
MyListOfImages brings up the 'Image Collection Editor' dialog, but using 'Add' button shows message:
'Cannot create an instance of System.Drawing.Image because it is an
abstract class.'
MyImageList shows an emtpy list, which cannot be edited.
My question is, if it's possible to tell VS Designer to use the 'Select resource' dialog when clicking 'Add' button and what needs to be done?
Starting from Marwie's comment, I was able to solve the problem.
There are three requirements that a collection should meet in order to be successfully persisted with the CollectionEditor:
The collection must implement the IList interface (inheriting from System.Collections.CollectionBase is in most of the cases the best option).
The collection must have an Indexer property.
The collection class must implement one or both of the following methods: Add and/or AddRange
So I created a class 'ImageItem' containing
an image
[Category("ImageItem")]
[DefaultValue(typeof(Image), null)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image Picture {
get { return m_Picture; }
set { m_Picture = value; }
}
a name (optional)
[Category("ImageItem")]
[DefaultValue(typeof(string), "")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Name {
get { return m_Name; }
set { m_Name = value; }
}
a value (optional)
[Category("ImageItem")]
[DefaultValue(typeof(int), "-1")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int Value {
get { return m_Value; }
set { m_Value = value; }
}
and a collection 'ImageCollection' holding instances of this class according to the conditions mentioned above:
public class ImageCollection : CollectionBase
public ImageItem this[int i]
public ImageItem Add(ImageItem item)
Then I created a control containing only this collection, initialized with one image:
public partial class MyControl: UserControl
{
public MyControl() {
InitializeComponent();
}
private ImageCollection m_MyImageCollection = new ImageCollection()
{ new ImageItem(0, "Failure", Properties.Resources.Cross), new ImageItem(1, "OK", Properties.Resources.Tickmark) };
[Browsable(true), Category("A Test"), DisplayName("Image Collection (ImageCollection)"), Description("Edit image collection")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor(typeof(System.ComponentModel.Design.CollectionEditor), typeof(System.Drawing.Design.UITypeEditor))]
public ImageCollection MyImageCollection {
get { return m_MyImageCollection; }
}
}
After compiling this code the designer shows that property. Now it is possible to add images using the common designer GUI controls.
I tried to change the default images compiled into this control when using it on my form, but I recognized, that the designer cannot remove content. It only stores the 'Add' action. So I modified the code to search within the collection for another item with the same ID. If there is one available, that instance is removed and replaced with the new one. Therefore I had to implement the AddRange method too.
public ImageItem Add(ImageItem item) {
for(int i = 0; i < InnerList.Count; i++) {
if(InnerList[i] is ImageItem) {
if(((ImageItem)InnerList[i]).Value == item.Value) {
InnerList.RemoveAt(i);
}
}
}
this.InnerList.Add(item);
return item;
}
public void AddRange(ImageItem[] array) {
foreach(ImageItem item in array) {
Add(item);
}
}
So my final classes are:
public class ImageItem {
private int m_Value = -1;
private string m_Name = "ImageItem";
private Image m_Picture = null;
[Category("ImageItem")]
[DefaultValue(typeof(int), "-1")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int Value {
get { return m_Value; }
set { m_Value = value; }
}
[Category("ImageItem")]
[DefaultValue(typeof(string), "")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string Name {
get { return m_Name; }
set { m_Name = value; }
}
[Category("ImageItem")]
[DefaultValue(typeof(Image), null)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Image Picture {
get { return m_Picture; }
set { m_Picture = value; }
}
public ImageItem() { }
public ImageItem(int value, string name, Image image) {
this.m_Value = value;
this.m_Name = name;
this.m_Picture = image;
}
}
And ImageCollection:
public class ImageCollection : CollectionBase {
public ImageCollection() {}
public ImageItem this[int i]
{
get { return (ImageItem)this.InnerList[i]; }
set { this.InnerList[i] = value; }
}
public ImageItem Add(ImageItem item) {
for(int i = 0; i < InnerList.Count; i++) {
if(InnerList[i] is ImageItem) {
if(((ImageItem)InnerList[i]).Value == item.Value) {
InnerList.RemoveAt(i);
}
}
}
this.InnerList.Add(item);
return item;
}
public void AddRange(ImageItem[] array) {
foreach(ImageItem item in array) {
Add(item);
}
}
public void Remove(ImageItem item) {
this.InnerList.Remove(item);
}
public bool Contains(ImageItem item) {
return this.InnerList.Contains(item);
}
public ImageItem[] GetValues() {
ImageItem[] item= new ImageItem[this.InnerList.Count];
this.InnerList.CopyTo(0, item, 0, this.InnerList.Count);
return item;
}
protected override void OnInsert(int index, object value) {
base.OnInsert(index, value);
}
}
I've got another answer from MSDN:
How to edit UserControl attribute of type ImageList in Designer PropertyGrid (add/remove/exchange images)
I will describe the idea in short. First create a new control with an ImageList attribute.
public partial class NewControl : UserControl {
public NewControl() {
InitializeComponent();
}
public ImageList MyImageList { get; set; } = null;
}
Then drag this control on any form.
Additionally drag an ImageList control from Toolbox onto this
form - I called it 'MyImages'.
Edit MyImages → Images with designer.
Assign 'MyImages' to NewControl's instance attribute MyImageList in property grid
The only drawback I see here is, that if the control already has an initialized ImageList attribute, the designer cannot handle it. If you try to edit MyImageList before you assigned another list, the designer shows the controls default list, that comes with the control. But it's not possible to edit that list.
This solution is much easier to deal with and much shorter than the first solution above, so that I prefer it more.
I have a use case which I would assume is pretty standard, however I haven't been able to find an example on exactly how to do this, or if it's possible.
Let's assume I have the following TableView
First Name Last Name Street NewRecord
Tom Smith Main St. Yes
Mike Smith First St. No
In this case, the grid should have the first three cells editable since the record is new, however when the record is not new then the Last Name cell should be disabled.
I tried this in the CellFactory and RowFactory - but haven't seen a way to accomplish this.
Thanks for your help.
The easiest way to do this is with a third-party binding library: ReactFX 2.0 has this functionality built-in, as described here. Using that you can do
TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
lastNameColumn.setEditable(true);
lastNameColumn.setCellFactory(tc -> {
TableCell<Person, String> cell = new TextFieldTableCell<>();
cell.editableProperty().bind(
// horrible cast needed because TableCell.tableRowProperty inexplicably returns a raw type:
Val.flatMap(cell.tableRowProperty(), row -> (ObservableValue<Person>)row.itemProperty())
.flatMap(Person::newRecordProperty)
.orElseConst(false));
return cell ;
});
(assumes a Person table model object with the obvious JavaFX properties and methods).
Without the library, you need a pretty miserable nested list of listeners:
TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
lastNameColumn.setEditable(true);
lastNameColumn.setCellFactory(tc -> {
TableCell<Person, String> cell = new TextFieldTableCell<>();
ChangeListener<Boolean> newRecordListener = (obs, wasNewRecord, isNewRecord) -> updateEditability(cell);
ChangeListener<Person> rowItemListener = (obs, oldPerson, newPerson) -> {
if (oldPerson != null) {
oldPerson.newRecordProperty().removeListener(newRecordListener);
}
if (newPerson != null) {
newPerson.newRecordProperty().addListener(newRecordListener);
}
updateEditability(cell);
};
ChangeListener<TableRow> rowListener = (obs, oldRow, newRow) -> {
if (oldRow != null) {
((ObservableValue<Person>)oldRow.itemProperty()).removeListener(rowItemListener);
if (oldRow.getItem() != null) {
((Person)oldRow.getItem()).newRecordProperty().removeListener(newRecordListener);
}
}
if (newRow != null) {
((ObservableValue<Person>)newRow.itemProperty()).addListener(rowItemListener);
if (newRow.getItem() != null) {
((Person)newRow.getItem()).newRecordProperty().addListener(newRecordListener);
}
}
updateEditability(cell);
};
cell.tableRowProperty().addListener(rowListener);
return cell ;
});
and then
private void updateEditability(TableCell<Person, String> cell) {
if (cell.getTableRow() == null) {
cell.setEditable(false);
} else {
TableRow<Person> row = (TableRow<Person>) cell.getTableRow();
if (row.getItem() == null) {
cell.setEditable(false);
} else {
cell.setEditable(row.getItem().isNewRecord());
}
}
}
An alternative using a "legacy style" API is
TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
lastNameColumn.setEditable(true);
lastNameColumn.setCellFactory(tc -> {
TableCell<Person, String> cell = new TextFieldTableCell<>();
cell.editableProperty().bind(
Bindings.selectBoolean(cell.tableRowProperty(), "item", "newRecord"));
return cell ;
});
I dislike this option, because it lacks any type safety (or indeed any compiler checks at all), and additionally in some earlier versions of JavaFX would generate almost endless warning messages if any of the properties in the "chain" had null values (which they will, frequently, in this case). I believe the latter issue is fixed, but the ReactFX version of this is far better, imho.
You can always stop an editing by setting event handler to check if the action is legal.
columnName.setOnEditStart(
new EventHandler<CellEditEvent<ItemClass, String>>(){
#Override
public void handle(CellEditEvent<ItemClass, String> event) {
if(event.getTableColumn().getCellData(3).compareTo("yes") == 0) {
event.getTableView().edit(-1, null);
//this prevents the editing in "progress"
}
}
}
);
I am unable to figure out how could I add a double click event to the cell of the CellTable.
Is it possible with GWT CellTable or not?
Is there any workaround
thank you..
al
BTW, i saw this post but there is no reply...
http://www.devcomments.com/Adding-DoubleClicks-and-OnContextMenu-to-CellTable-at1066168.htm
I crafted something different that just fit my needs:
cellTable.addCellPreviewHandler(new Handler<TitoloProxy>() {
long lastClick=-1000;
#Override
public void onCellPreview(CellPreviewEvent<TitoloProxy> event) {
long clictAt = System.currentTimeMillis();
GWT.log("clickAt: "+(clictAt));
GWT.log("lastClick: "+(lastClick));
if(event.getNativeEvent().getType().contains("click")){
GWT.log(""+(clictAt-lastClick));
if(clictAt-lastClick < 300) { // dblclick on 2 clicks detected within 300 ms
Window.alert("I am a double click crafted event!");
}
lastClick = System.currentTimeMillis();
}
}
});
cellTable.addDomHandler(new DoubleClickHandler() {
#Override
public void onDoubleClick(DoubleClickEvent event) {
Window.alert("That's it!");
}
}, DoubleClickEvent.getType());
Integer row=0;// to hold row index
Integer column=0;// to hold column index
_Grid.addCellPreviewHandler(new CellPreviewEvent.Handler<Model>() {
// this is to handle row id
#Override
public void onCellPreview(final CellPreviewEvent<Model> event) {
if (BrowserEvents.CLICK.equalsIgnoreCase(event.getNativeEvent().getType())) {
row = event.getIndex();
column=event.getColumn();
}
}
});
// because Doubleclick handler doesn't give row index or column index we will use addCellPreviewHandler to return row index or column index.
_Grid.addDomHandler(new DoubleClickHandler() {
#Override
public void onDoubleClick(final DoubleClickEvent event) {
System.out.println(" You clicked row = " + row);
System.out.println(" You clicked column = " + column);
}
}, DoubleClickEvent.getType());
For cell lists, this code works ok:
cellList.addDomHandler(new DoubleClickHandler() {
#Override
public void onDoubleClick(DoubleClickEvent event) {
// do the stuff
}
}, DoubleClickEvent.getType());
I'm not sure about table cells
Because the CellPreview interface does not natively capture double click events you will need add event logic into the Overriden onCellPreview method. First you would think the best way would be to check the click time differences. However it is much more efficient and elegant to use a state machine and count clicks. This is more robust and allows you to deal with multiple event cases - Such as mouse hover, single, and double clicks. The code is pretty straightforward. So enjoy!
public class CellHoverHandler implements Handler<T> {
Timer singleClickTimer;
int clickCount = 0;
int clickDelay = 300;
#Override
public void onCellPreview(final CellPreviewEvent<T> event) {
if (Event.getTypeInt(event.getNativeEvent().getType()) == Event.ONMOUSEOVER) {
handleOnMouseOver(event);
} else if (Event.getTypeInt(event.getNativeEvent().getType()) == Event.ONCLICK) {
clickCount++;
if (clickCount == 1) {
singleClickTimer = new Timer() {
#Override
public void run() {
clickCount = 0;
handleOnClick(event);
}
};
singleClickTimer.schedule(clickDelay);
} else if (clickCount == 2) {
singleClickTimer.cancel();
clickCount = 0;
handleOnDblClick(event);
}
}
}
private void handleOnMouseOver(CellPreviewEvent<T> event) {
Element cell = event.getNativeEvent().getEventTarget().cast();
GWT.log("mouse over event");
}
private void handleOnClick(CellPreviewEvent<T> event) {
Element cell = event.getNativeEvent().getEventTarget().cast();
GWT.log("click event");
}
private void handleOnDblClick(CellPreviewEvent<T> event) {
Element cell = event.getNativeEvent().getEventTarget().cast();
GWT.log("double click event");
}
OPTIMIZATION: feel free to stick the count, timer, and delay as static class members or global members to reuse. Also check to see if the timer is null before making a new instance. I had omitted this for simplicity. Unlike a lot of other techniques this way still provides you with easy and direct access to the cell event. The technique with overloading the AbstractCell works well too, however sometimes you really don't have custom cells or want to make a custom cell to just handle events on the cell.
Leaving this here for future reference
private Set<GenericEventHandler<T>> dblClickHandlers = new HashSet<>(4);
dblClickHandlers simply maps interface implementations of my choice
table.addCellPreviewHandler(event -> {
if (BrowserEvents.DBLCLICK.equalsIgnoreCase(event.getNativeEvent().getType())) {
LOGGER.info("dblclick (native) " + event.getIndex() + " " + event.getColumn() + "; " + event.getValue());
dblClickHandlers.forEach(handler -> {
handler.onEvent(event.getValue());
});
}
});
table.sinkBitlessEvent(BrowserEvents.DBLCLICK);
The trick is to sink the 'dblclick' event.
If you wanted a text cell that allows you to support your own chosen list of events, you can use this:
public class EventfulTextCell extends AbstractSafeHtmlCell`<String`> {
private static final String[] NO_CONSUMED_EVENTS = null;
public EventfulTextCell() {
this(NO_CONSUMED_EVENTS);
}
public EventfulTextCell(String... consumedEvents) {
super(SimpleSafeHtmlRenderer.getInstance(), consumedEvents);
}
#Override
public void render(Context context, SafeHtml value, SafeHtmlBuilder sb) {
if (value != null) {
sb.append(value);
}
}
}
Then you instantiate it:
new EventfulTextCell("click", "dblclick")
Then override the onBrowserEvent() method to process your events.
I am having three EditField i have a set focuslistener for the second editfield alone. when the focus is lost i am checking whether the field is empty or not if the field is empty it will show the popup message .Now the problem is once the user came to that field he could not move to other fields without entering any fields but i want user can move to the field which is above to that field and he could not to move to the below field.How to handle this situation? Please share ur ideas.
I suppose it's for validation functionality?
See proposed validation strategy: we have interface IValidated which can be checked for proper data:
interface IValidated {
public boolean validate();
}
Now, any field, like list or checkbox or choice can implement it.
Next, there is onFocus event in each field, there you can validate previouse IValidated field and setFocus back if it's fail.
See EditField example:
class ValidatedEdit extends BasicEditField implements IValidated {
private static final String EMPTY_STRING = "";
public ValidatedEdit(String label, String value) {
super(label, value);
}
protected void onFocus(int direction) {
// it's from upper field
if (direction > 0 && getIndex() > 0) {
// get upper field
Field field = getManager().getField(getIndex() - 1);
// if its IValidated
if (field instanceof IValidated) {
IValidated validated = (IValidated) field;
// validate, if false set focus to IValidated
if (!validated.validate()) {
field.setFocus();
return;
}
}
}
super.onFocus(direction);
}
public boolean validate() {
return !getText().equalsIgnoreCase(EMPTY_STRING);
}
}
Example of use:
class Scr extends MainScreen {
ValidatedEdit mEditField1 = new ValidatedEdit("field#1", "");
ValidatedEdit mEditField2 = new ValidatedEdit("field#2", "");
ValidatedEdit mEditField3 = new ValidatedEdit("field#3", "");
public Scr() {
add(mEditField1);
add(mEditField2);
add(mEditField3);
}
}
How can all checked items from a list can be fetched?
I need to get all selected (checked) items from the list and populate a vector.
I am not getting all selected items, I am getting only the item on which current focus is.
I am implementing listfield with checkboxes as per the knowledgebase article.
If I use getSelection(), it is returning me the currently highlighted list row index, and not all that have been checked.
As I undestood, sample is How To - Create a ListField with check boxes
Then you can add Vector to the class where ListFieldCallback is implemented:
private Vector _checkedData = new Vector();
public Vector getCheckedItems() {
return _checkedData;
}
and update drawListRow this way:
if (currentRow.isChecked())
{
if( -1 ==_checkedData.indexOf(currentRow))
_checkedData.addElement(currentRow);
rowString.append(Characters.BALLOT_BOX_WITH_CHECK);
}
else
{
if( -1 !=_checkedData.indexOf(currentRow))
_checkedData.removeElement(currentRow);
rowString.append(Characters.BALLOT_BOX);
}
If you would use VerticalFieldManager with custom CheckBoxField, you could iterate over all fields on screen (or any manager) and check if its' checkbox field, then take a value:
class List extends VerticalFieldManager {
...
public Vector getCheckedItems() {
Vector result = new Vector();
for (int i = 0, cnt = getFieldCount(); i < cnt; i++) {
Field field = getField(i);
if (field instanceof CheckboxField) {
CheckboxField checkboxField = (CheckboxField) field;
if (checkboxField.isChecked())
result.addElement(checkboxField);
}
}
return result;
}
}
#sandhya-m
class List extends VerticalFieldManager {
...
public void selectAll() {
for (int i = 0, cnt = getFieldCount(); i < cnt; i++) {
Field field = getField(i);
if (field instanceof CheckboxField) {
CheckboxField checkboxField = (CheckboxField) field;
checkboxField.setChecked(true);
}
}
}
}