DPad Nav Preference Screen Google TV - settings

I have preferences created from XML and preferenceFragment and left-hand navigation. When I choose "Settings" my pref fragments comes out...I can use DPad to move up and down, when there are no more choices on the bottom, the cursor stops (desired behavior), but when I move up the list, the cursor continues over to the left-hand nav. I want it to stop at the top. Below is my pref_screen xml. My left hand nav is created code side. I have no idea how to work DPad logic into this. I also want to restrict the DPad from moving cursor over to left-hand navigation menu if in settings fragment.
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="main_pref">
<PreferenceCategory
android:title="#string/units">
<ListPreference
android:key="pref_unit_temp"
android:title="#string/temperature"
android:defaultValue="#string/default_metric"
android:entries="#array/pref_temp_units"
android:entryValues="#array/pref_temp_units_values"
android:dialogTitle="#string/units"
android:layout="#layout/settings_item" />
</PreferenceCategory>
<PreferenceCategory
android:title="#string/advanced">
<ListPreference
android:key="pref_speed"
android:title="#string/speed"
android:entries="#array/pref_speed"
android:entryValues="#array/pref_speed_values"
android:defaultValue="#string/default_metric"
android:layout="#layout/settings_item"/>
<ListPreference
android:key="pref_measurement"
android:title="#string/measurement"
android:entries="#array/pref_measurement"
android:entryValues="#array/pref_measurement_values"
android:defaultValue="#string/default_metric"
android:layout="#layout/settings_item"/>
<ListPreference
android:key="pref_time"
android:title="#string/time_format"
android:entries="#array/pref_time"
android:entryValues="#array/pref_time_values"
android:defaultValue="#string/default_metric"
android:layout="#layout/settings_item"/>
<ListPreference
android:key="pref_date"
android:title="#string/date_format"
android:entries="#array/pref_date"
android:entryValues="#array/pref_date_values"
android:defaultValue="#string/default_metric"
android:layout="#layout/settings_item"/>
</PreferenceCategory><!--
<PreferenceCategory
android:title="Weather Notification Bar">
<CheckBoxPreference
android:key="pref_notification_enable"
android:title="Enable Notifications"
android:summary="Turn on/off status bar weather notifications"
android:defaultValue="true"
android:enabled="false"/>
<ListPreference
android:key="pref_notification_update_method"
android:dependency="pref_notification_enable"
android:title="Choose notification update method"
android:summary="Manual, Automatic, Interval"
android:entries="#array/pref_notification_update_method"
android:entryValues="#array/pref_notification_update_method_values"
android:dialogTitle="Update Method" />
<ListPreference
android:key="pref_notification_interval"
android:title="Interval to update"
android:dependency="pref_notification_enable"
android:summary="Use this to set the update interval if interval method is selected"
android:entries="#array/pref_notification_interval"
android:entryValues="#array/pref_notification_interval_values"
android:dialogTitle="Update Interval" />
</PreferenceCategory>
-->
<!--<PreferenceCategory
android:title="Background">
<CheckBoxPreference
android:key="background_enabled"
android:title="Enable Interactive Background"
android:summary="Turn off to improve performance"
android:defaultValue="true"
android:enabled="true"/>
</PreferenceCategory>
--><PreferenceCategory
android:title="AccuWeather">
<!-- <PreferenceScreen
android:title="#string/call_us"
android:summary=""
android:layout="#layout/settings_item">
<intent android:action="android.intent.action.DIAL"
android:data="tel:8142358650" />
</PreferenceScreen>
<PreferenceScreen
android:title="#string/email_us"
android:summary="customerservice#accuweather.com"
android:layout="#layout/settings_item">
<intent android:action="com.accuweather.android.tablet.EMAIL_ACCUWX"
/>
<intent android:action="android.intent.action.SENDTO"
android:data="customerservice#accuweather.com"/>
</PreferenceScreen> -->
<PreferenceScreen
android:title="#string/terms_conditions"
android:summary=""
android:layout="#layout/settings_item">
<intent android:action="android.intent.action.VIEW"
android:data="http://www.accuweather.com/m/EULA.aspx" />
</PreferenceScreen>
</PreferenceCategory>
</PreferenceScreen>
Here is a screen shot:

I had to figure out a way to depict the positions and children to handle dpad. Here is the code I wrote to handle this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState){
View v = inflater.inflate(R.layout.settings_tile, null);
final ImageView closeBtn = (ImageView) v.findViewById(R.id.settingsCloseButton);
closeBtn.setFocusable(true);
closeBtn.setOnClickListener(this);
closeBtn.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
v.setOnKeyListener(new OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
return false;
}
if (event.getAction() == KeyEvent.ACTION_DOWN) {
return !(KeyEvent.KEYCODE_DPAD_DOWN == keyCode);
}
return false;
}
});
} else {
v.setOnKeyListener(null);
}
}
});
final ListView lv = (ListView) v.findViewById(android.R.id.list);
lv.setOnKeyListener(new OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch(keyCode) {
case KeyEvent.KEYCODE_DPAD_DOWN:
return !(lv.getSelectedItemPosition() < lv.getCount() - 1);
case KeyEvent.KEYCODE_DPAD_UP:
if (lv.getSelectedItemPosition() == 1) {
closeBtn.requestFocus();
}
return lv.getSelectedItemPosition() == 1;
case KeyEvent.KEYCODE_DPAD_RIGHT:
return true;
}
return false;
}
return false;
}
});
consumeKeyEvent(v);
return v;
}
private void consumeKeyEvent(Object obj) {
if (obj instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup)obj).getChildCount(); i++) {
consumeKeyEvent(((ViewGroup)obj).getChildAt(i));
}
return;
}
((View)obj).setOnKeyListener(new OnKeyListener() {
#Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
Log.i(DEBUG_TAG, "Event consumed, " + keyCode);
return false;
}
});
}

Related

Bizzare behavior of NotifyDataSetChanged() Xamarin.Android

I am working on an application that asks the user to provide photos for items.
My main object is following
public class PickedObject
{
int ID { get; set; }
int Name{ get; set; }
bool HasPhotos { get; set; }
}
And I have another table for Photos since one item can have multiple photos.
What's happening is that in my adapter, I have created a recycler view so that if an Item has images, there should be an ImageButton visible in front of it.
Here is my row template
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="8">
<TextView
android:id="#+id/lblItemName"
android:layout_width="0dp"
android:layout_weight="6"
android:layout_marginTop="5dp"
android:layout_marginLeft="10dp"
android:layout_height="wrap_content"
android:textSize="20dp"
android:text="" />
<ImageButton
android:layout_weight="1"
android:layout_width="35dp"
android:layout_height="35dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:layout_marginTop="5dp"
android:background="#android:color/transparent"
android:padding="5dp"
android:src="#drawable/rowcamera"
android:id="#+id/btnCamera" />
<ImageButton
android:layout_weight="1"
android:layout_width="35dp"
android:layout_height="35dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:layout_marginTop="5dp"
android:background="#android:color/transparent"
android:padding="5dp"
android:src="#drawable/rowpicture"
android:id="#+id/btnPicture" />
</LinearLayout>
In my adapter I am using the following in GetView(int position, View convertView, ViewGroup parent)
public override View GetView(int position, View convertView, ViewGroup parent)
{
ItemViewHolder holder = null;
PickedObject item = _items[position];
View view = convertView;
try
{
if (view != null)
holder = (ItemViewHolder)view.Tag; //Holder
if(holder == null)
{
holder = new ItemViewHolder();
view = _context.LayoutInflater.Inflate(Resource.Layout.ItemRow, null);
holder.ItemName = view.FindViewById<TextView>(Resource.Id.lblItemName);
holder.CameraButton = view.FindViewById<ImageButton>(Resource.Id.btnCamera);
holder.CameraButton.Tag = item.ID;
holder.PictureButton = view.FindViewById<ImageButton>(Resource.Id.btnPicture);
holder.PictureButton.Tag = item.ID;
CameraClickListener cameraListener = new CameraClickListener(_context, this);
cameraListener.CameraClickEvent += CameraClickedEvent;
holder.CameraButton.SetOnClickListener(cameraListener);
ImageClickListener imageClickListener = new ImageClickListener(_context);
imageClickListener.ImageClickEvent += ImageClickedEvent;
holder.PictureButton.SetOnClickListener(imageClickListener);
view.Tag = holder;
}
holder.ItemName.Text = item.Name;
holder.CameraButton.Visibility = ViewStates.Visible;
if (item.HasPhotos)
{
holder.PictureButton.Visibility = ViewStates.Invisible;
}
else
{
holder.PictureButton.Visibility = ViewStates.Visible;
}
}
catch(Exception ex)
{
Log.Error("Item adapter", ex.Message);
}
return view;
}
Now in my MainActivity I have called two activities (for result), One is the camera activity that starts the camera and saves the photo.
The other activity is a gallery type activity that is created within the project.
The activity on result is called as follows:
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
int itemId = CameraApp.ID; // Camera App is a static object to hold data
if (requestCode == General.CAMERA_ACTIVITY) // Camera activity code 110
{
CompleteCameraActivity(resultCode, data); // Save picture into gallery and update db
}
if(requestCode == General.GALLERY_ACTIVITY) // Gallery activity code 113
{
CompleteGalleryActivity(resultCode, data);
}
SectionFragment frag = (SectionFragment)sectionsAdapter.GetItem(viewPager.CurrentItem);
int sectionId = frag.Section.ID;
frag.Adapter.SetPicture(questionId, dataAccess.HasPictures(itemId));
}
The SetPicture method in the adapter is as follows:
public void SetPicture(int id, bool hasPics)
{
Item itm = _items.SingleOrDefault(a => a.ID == id);
if (itm != null)
_items.SingleOrDefault(a => a.ID == id).HasPhotos = hasPics;
this.NotifyDataSetChanged();
}
The application works fine when it comes from Camera activity. It updates the record and also the image button is visible as well.
The trouble comes when the MainActivity gains control after GalleryActivity. When I delete all the images in the gallery the SetPicture method is executed which makes the HasPhotos property to false. But the NotifyDataSetChanged part of the adapter doesn't work (when I test it in debugger the GetView method is not fired for the adapter).
So what happens is that my ListView remains in a state where even if the item has no photos the ImageButton (for pictures) is still available.
Can someone please let me know what I am doing wrong?
EDIT
I noticed that if I scroll the list once and return back to the item, the ImageButton is invisible, that means that it is working but why doesn't it work in StartActivityForResult?

JavaFX - weird behavior of TreeView on removal of the selected item

In my application, I have a TreeView at left side, and I update the pane at the right side according to the selection in TreeView. A very straight forward scenario. When the selection is null, I show a message like "please make a selection" in the pane, i.e. I also handle null selection in the TreeView.
During the life time of the application, some items can be added to/removed from the TreeView. I had some problems when the selected item in the TreeView is removed. In this case, I expected the selection of the TreeView to become null, however it is not!
To debug this case, I writed a simple FXML application, as below:
FXMLDocument.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane id="AnchorPane" prefHeight="350.0" prefWidth="250.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="treeviewbug.FXMLDocumentController">
<children>
<TreeView fx:id="treeView" prefHeight="350.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="50.0" />
<Button mnemonicParsing="false" onAction="#update" text="update" AnchorPane.leftAnchor="20.0" AnchorPane.topAnchor="15.0" />
<Label fx:id="selectionLabel" text="" AnchorPane.leftAnchor="100.0" AnchorPane.rightAnchor="20.0" AnchorPane.topAnchor="20.0" />
</children>
</AnchorPane>
FXMLDocumentController.java:
package treeviewbug;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.HBox;
import javafx.util.Callback;
public class FXMLDocumentController implements Initializable {
#FXML
Label selectionLabel;
#FXML
private TreeView<String> treeView;
private TreeItem<String> selectedItem = null;
private ChangeListener<String> changeListener = new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> ov, String oldValue, String newValue) {
selectionLabel.setText(newValue);
}
};
#Override
public void initialize(URL url, ResourceBundle rb) {
final TreeItem<String> root = new TreeItem<>();
for (int i = 1; i <= 20; i++) {
root.getChildren().add(new TreeItem<String>("Item " + i));
}
treeView.setShowRoot(false);
treeView.setRoot(root);
treeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TreeItem<String>>() {
#Override
public synchronized void changed(ObservableValue<? extends TreeItem<String>> ov, TreeItem<String> oldSelection, TreeItem<String> newSelection) {
if (selectedItem != null) {
selectedItem.valueProperty().removeListener(changeListener);
}
if (newSelection == null) {
selectionLabel.setText("selection is null");
} else {
selectionLabel.setText(newSelection.getValue());
}
selectedItem = newSelection;
if (selectedItem != null) {
selectedItem.valueProperty().addListener(changeListener);
}
}
});
treeView.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
#Override
public TreeCell<String> call(TreeView<String> p) {
System.out.println("Creating new cell.");
return new TreeCell<String>() {
Label label = new Label();
Button button = new Button("remove");
HBox box = new HBox(20);
{
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
TreeItem<String> itemToRemove = null;
for (TreeItem<String> item : root.getChildren()) {
if (item.getValue().equals(getItem())) {
itemToRemove = item;
break;
}
}
if (itemToRemove != null) {
root.getChildren().remove(itemToRemove);
}
t.consume();
}
});
box.getChildren().addAll(label, button);
}
#Override
protected void updateItem(String value, boolean bln) {
super.updateItem(value, bln);
if (value != null) {
label.setText(value);
setGraphic(box);
} else {
setGraphic(null);
}
}
};
}
});
}
#FXML
private void update() {
TreeItem<String> i = treeView.getSelectionModel().getSelectedItem();
if (i == null) {
selectionLabel.setText("selection is null");
} else {
selectionLabel.setText(i.getValue());
}
}
}
Initially, the TreeView is populated with 20 items. A listener is attached to selectedItemProperty in case the user clicks on some TreeItem. A listener is also attached to the valueProperty of the selected TreeItem in case the user does not click on some item but the value of the selected item changes for some reason. The user can remove a particular item by clicking the remove button within it. At any time, the user can click update button to update the label content that is showing the current selection, in case some event is missed by my handlers.
In this simple test app, when I remove the selected item, it sometimes shows the item just before the removed one as the selected item, and sometimes shows the item just after the removed one as the selected item. However, the selected item does not change most of time, even if it is not contained in the TreeView any more!
My first question is, is this normal, or a bug? Have you ever seen something like this?
As a workaround, I added the following code right after the for loop:
root.getChildren().addListener(new ListChangeListener<TreeItem<String>>() {
#Override
public void onChanged(ListChangeListener.Change<? extends TreeItem<String>> change) {
while (change.next()) {
if (change.wasRemoved() && selectedItem != null) {
if (change.getRemoved().contains(selectedItem)) {
selectedItem.valueProperty().removeListener(changeListener);
selectedItem = null;
treeView.getSelectionModel().clearSelection();
}
}
}
}
});
Now, there is no weird situation like having a nonexisting item as the selected item. But sometimes the selection is not null, although I call clearSelection(). My second question is, is this auto-selection normal?
Final question, is there a better workaround?
Sorry, it was a very long question. Thank you if you are still reading :)
In Java 1.8.0_92 the behaviour seems to be:
if an item behind the selection is deleted selectedItem and selectedIndex stay the same,
if an item before the selection is deleted selectedItem stays the same but selectedIndex changes (because selectedItem changed its position),
if the selected item is deleted selectedIndex is decremented and selectedItem changes analogous; only if the first item is deleted selectedIndex stays the same.
The only weird behaviour occurs if the last item is deleted. In this case selectedIndex and selectedItem do not change, as Gman also remarked. This leads to an no longer existing item to be selected (seems to be a bug).
As a workaround you can check the root-children in your deletion-code.
The onAction for the delete-button could look like this:
button.setOnAction(t -> {
TreeItem<String> item = getTreeItem();
item.getParent().getChildren().remove(item); // remove item
if(treeView.getRoot().getChildren().size() == 0) { // check for empty tree
treeView.getSelectionModel().clearSelection();
}
t.consume();
});
Indeed the selectedItemProperty() is not updated, however the getSelectedIndices() is updated correctly. So you can add a listener to getSelectedIndices().
I use the following code to keep an observableList of selectedItems.(of type Folder)
cnt_treeView.getSelectionModel().getSelectedIndices().addListener((InvalidationListener) observable -> {
ArrayList<Folder> tmmp = new ArrayList<>();
for (TreeItem<Folder> ti : cnt_treeView.getSelectionModel().getSelectedItems()) {
if (ti != null)
tmmp.add(ti.getValue());
selectedFolders.setAll(tmmp);
}
});
Found this solution on
https://community.oracle.com/tech/developers/discussion/2393837/change-listener-not-notified-when-tree-item-is-deselected
I had the same issue, you should pass an observable collection to your root node.
In your code, instead of:
final TreeItem<String> root = new TreeItem<>();
for (int i = 1; i <= 20; i++) {
root.getChildren().add(new TreeItem<String>("Item " + i));
}
treeView.setShowRoot(false);
treeView.setRoot(root);
add following properties:
private ObservableList<TreeItem<String>> obsTreeItems;
private List<TreeItem<String>> treeItems;
and change your first lines with:
treeItems = new ArrayList<>();
final TreeItem<String> root = new TreeItem<>();
for (int i = 1; i <= 20; i++) {
/*root.getChildren().add(new TreeItem<String>("Item " + i));*/
treeItems.add(new TreeItem<String>("Item "+i));
}
//observable collection
obsTreeItems = FXCollections.observableArrayList(treeItems);
//add items as observable list to root
root.getChildren().addAll(obsTreeItems);
treeView.setShowRoot(false);
treeView.setRoot(root);
You'll notice that the update will be done well this time. You should remove aswel all the workaround code. The update button will not be needed anymore.

buttonlistening - how to make it professional?

I want to implement a GUI element that works like a button that reacts different on right/left clicks and dragging.
Say this button is an object. Who is in charge to call the right method on mouseevents from the button and how is it designed in usual programming languages like e.g. AWT in Java?
I don't claim that this is 'the professional way' but this is how I do it.
With lazy initialization and anonymous classes.
private JButton btnSpecialbutton;
private JButton getBtnSpecialbutton() {
if (btnSpecialbutton == null) {
btnSpecialbutton = new JButton("SpecialButton");
btnSpecialbutton.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
System.out.println("Dragged");
}
});
btnSpecialbutton.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1)
{
System.out.println("Left Mouse Button");
}
else if(e.getButton() == MouseEvent.BUTTON3)
{
System.out.println("Right Mouse Button");
}
}
});
}
return btnSpecialbutton;
}

Prevent selection of Tree Cell from right-click on Tree View Cell

I'm developing a custom TreeView object.
I'm using a custom cellFactory to provide the TreeCell objects of my TreeView.
This allows me to install custom Context Menu on the various cells, depending on the Item they are displaying.
But I'm not entirely satisfied with the behaviour.
When left-clicking on cell, it gets selected (OK)
But when right-clicking a cell, the context menu is displayed (OK) but the cell is also selected. (NOK)
How can I change this behaviour ?
I tried to implement an eventFilter on the tree view, to consume the event if it is a right-click but this doesn't change anything, the above behaviour still applies.
addEventFilter(MouseEvent.MOUSE_CLICKED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getButton() == MouseButton.SECONDARY) {
event.consume();
}
}
});
setCellFactory(new Callback<TreeView<TreeDisplayable>, TreeCell<TreeDisplayable>>() {
#Override
public TreeCell<TreeDisplayable> call(
final TreeView<TreeDisplayable> treeView) {
return new TreeDisplayableTreeCell(owner, javaModel);
}
});
public class TreeDisplayableTreeCell extends TreeCell<TreeDisplayable> {
...
#Override
public void updateItem(TreeDisplayable item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
setText(getItem().treeViewString());
setGraphic(item.getPic());
if (getTreeItem().getParent() == null) {
// it means it's the root node
setContextMenu(new RootItemContextMenu(javaModel, owner));
} else {
setContextMenu(new TreeItemContextMenu(javaModel, owner,getTreeItem().getValue()));
}
}
}
}
Reacting on Tony's comment
Creating a custom EventDispatcher does the trick.
public class TreeEventDispatcher implements EventDispatcher {
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
if (mouseEvent.getButton() == MouseButton.SECONDARY) {
event.consume();
} else {
event = tail.dispatchEvent(event);
}
} else {
event = tail.dispatchEvent(event);
}
return event;
}
}
The behaviour is identical for all events, except the right click event, which is consumed, thus preventing the right-click selection of any TreeCell.
Luckily enough, the context menu is still displayed on right click (although I don't understand why ...) Does anybody have a clue ?
Previous Facewindu answer is actually working, but there is another way to achieve that behavior and still have context menu appearing on right click:
treeView.addEventFilter(MOUSE_PRESSED, event -> {
if (event.isSecondaryButtonDown()) {
Node text = (Node) event.getTarget();
TreeCell<...> treeCell = (TreeCell<...>) text.getParent();
treeCell.getContextMenu().show(treeCell, 0, 0);
event.consume();
}
});

javafx, TableView: detect a doubleclick on a cell

Given a TableView, i need to detect the doubleclick on a cell.
tableView.setOnMouseClicked(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
if(event.getClickCount()>1)
{
System.out.println("double clicked!");
}
}
});
How to determine the cell on which the mouse has been clicked?
Code example.
Run the "Example 12-11: Alternative Solution Of Cell Editing" of official tableview tutorial.
Replace the followings:
table.setEditable(false);
Callback<TableColumn, TableCell> cellFactory =
new Callback<TableColumn, TableCell>() {
public TableCell call(TableColumn p) {
TableCell cell = new TableCell<Person, String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? null : getString());
setGraphic(null);
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
};
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() > 1) {
System.out.println("double clicked!");
TableCell c = (TableCell) event.getSource();
System.out.println("Cell text: " + c.getText());
}
}
});
return cell;
}
};
No need to EditingCell since your cells are uneditable. Cell factory is used for cell rendering. So one can put any node/control other than default Labeled using cell's setGraphics() method. IMO you cannot access the default cell directly so you should define your own cell factory to be able to put event filter on cell.
JavaFX allows you to set up multiple listeners per cell (I'm not saying that this is good or bad, just that you can). Each listener will execute your code if you have code set to execute a response to the specific listener for the specific column/row. To capture cell mouse clicks, I use the following:
table.setEditable(true);
table.getSelectionModel().setCellSelectionEnabled(true); // selects cell only, not the whole row
table.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent click) {
if (click.getClickCount() == 2) {
#SuppressWarnings("rawtypes")
TablePosition pos = table.getSelectionModel().getSelectedCells().get(0);
int row = pos.getRow();
int col = pos.getColumn();
#SuppressWarnings("rawtypes")
TableColumn column = pos.getTableColumn();
String val = column.getCellData(row).toString(); System.out.println("Selected Value, " + val + ", Column: " + col + ", Row: " + row);
if ( col == 2 ) { ... do something ... }
if ( col == 5 ) { ... do something ... }
if ( col == 6 ) { ... do something ... }
if ( col == 8 ) { ... do something ... }
}
}
});
You can see from the above code, on the columns I want to do something based on a mouse click, I have code:
if ( col == <int> ) { ... do something ... }
I also have those columns set to not allow editing:
thisCol.setEditable(false);
The rows that I want to edit I have .setEditable(true) but don't have a response included with a mouse click.
Cell editing defaults to 2 mouse clicks. You can change the above code to capture different mouse events on a cell, so you can still edit the cell with 2 mouse clicks, or open a URL, dialog box, etc., with any other mouse event determined by you. TableView allows you to determine your own functionality based on your imagination and programming skills. You're not stuck with "I can either edit it, or fire a mouse event with it." You can do both :)
Add the following in the body of your listener, with T the type of your table record :
#SuppressWarnings("rawtypes")
ObservableList<TablePosition> cells = tableView.getSelectionModel().getSelectedCells();
for( TablePosition< T, ? > cell : cells )
{
System.out.println( cell.getColumn());
}// for
Create your cell using a cell factory and in the cell factory which creates the cell node, place an mouse event handler or filter on the node rather than the tableView.
In my case i use next code
tableViewObject.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
if (t.getClickCount() == 2 && getSelectedItem() != null) {
SMPBLogger.logInfo("Double cliked", Boolean.TRUE);
if (listener != null) {
listener.doubleClicked(tableViewObject.this,getSelectedItem());
}
}
}
});

Resources