custom grid view, with image and checkbox crashing - android-gridview

in MainActivity.java I am reading a list from a txt file then on a show list button I am able to display the list, with list items, images and checkbox using custom grid view. Now with another button click I am trying to get checked item's position so as I can use these selected items. the code I have is giving me a null pointer exception error on . view.getCheckedItemPositions();
MainActivity.java
if(v.getId()==R.id.toast)
{
Toast.makeText(this,"positions" + adapter.getCheckedItemPositions(view), Toast.LENGTH_SHORT).show();
}
}
public class CustomAdapter extends BaseAdapter {
#Override
public View getView(int position, View view, ViewGroup parent) {
LinearLayout layout = new LinearLayout(getApplicationContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
view = new View(getApplicationContext());
TextView text=new TextView(getApplicationContext());
text.setTextColor(Color.BLACK);
text.setText(content.get(position));
CheckBox checkBox = new CheckBox(getApplicationContext());
checkBox.setChecked(!checkBox.isChecked());
checkBox.setChecked(true);
layout.addView(checkBox);
layout.addView(text);
return layout;
}
public List<Integer> getCheckedItemPositions(GridView view){
SparseBooleanArray checked = new SparseBooleanArray();
checked = view.getCheckedItemPositions();
List<Integer> positions = new ArrayList<>();
int checksize=checked.size();
for(int i=0; i<checksize; i++) {
if (checked.valueAt(i)) {
positions.add(checked.keyAt(i));
}
}
return positions;
}
}
}
Activity_main.xml
<GridView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/gridView"
android:layout_below="#+id/button"
android:layout_alignParentStart="true"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Toast"
android:id="#+id/toast"
android:layout_alignParentTop="true"
android:layout_alignEnd="#+id/gridView"
android:layout_marginEnd="49dp" />
</RelativeLayout>
logcat
--------- beginning of crash
09-18 18:31:49.144 31751-31751/com.example.administrator.myapplication E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.example.administrator.myapplication, PID: 31751
java.lang.NullPointerException: Attempt to invoke virtual method 'int android.util.SparseBooleanArray.size()' on a null object reference
at com.example.administrator.myapplication.MainActivity$CustomAdapter.getCheckedItemPositions(MainActivity.java:158)
at com.example.administrator.myapplication.MainActivity.onClick(MainActivity.java:96)
at android.view.View.performClick(View.java:4780)
at android.view.View$PerformClick.run(View.java:19866)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5257)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
09-18 18:32:26.135 31751-31758/com.example.administrator.myapplication W/art﹕ Suspending all threads took: 102.748ms
09-18 18:32:27.078 31751-31758/com.example.administrator.myapplication W/art﹕ Suspending all threads took: 78.288ms

I got the answer..
I created a link hash map with string as position of the check box and value as the boolean value at that position. Like
LinkedHashMap mylist = new LinkedHashMap<>();
Now in order to get the status of the checked item and monitor it in runntime we have to set a Listener on checkbox either using
checkBox.setOnCheckedChangeListener();
or //
checkbox.setOnClickListener()
either of these two works.
Earlier I was just setting up the checkbox but not setting up a listener on the check box as above
checkBox.setChecked(!checkBox.isChecked());
checkBox.setChecked(true);
Now with setOnClickListener all I have to do is store the position and boolean value of the checkbox to the LinkHashmap.
checkBox.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (((CheckBox) v).isChecked()) {
saveValues("check" + position, true);
Toast.makeText(MainActivity.this,"position: "+getValues("check"+position), Toast.LENGTH_SHORT).show();
}
else {
saveValues("check"+position,false);
}
}
});
Later on I can see the checked items using
public Boolean getValues(String a){
return mylist.get(a).booleanValue();
}
I am really thankful to the contributors of the below post
Android: checkbox listener

Related

Is there a way to update bounded data when I swipe back [PopAsync() ]

To provide some context, I'm writing a Xamarin.Forms application and utilizing data binding with INotifyPropertyChanged. Currently I have an inventory counter displayed on a button. The text on this button displays the bounded "Count" variable (e.g Current Inventory: 35). When I press the button, I push a screen onto the navigation stack which allows me to edit this "Count" variable. I use the class implementation like this
public class UserInventory : INotifyPropertyChanged
{
private int count = 0;
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public int Count
{
get => Preferences.Get(nameof(Count),0);
set
{
if (count == value || value <1)
return;
Preferences.Set(nameof(Count), value);
//count = value;
//Application.Current.Properties["Count"] = count;
OnPropertyChanged(nameof(Count));
//OnPropertyChanged(nameof(displayName));
}
}
public UserInventory()
{
}
void OnPropertyChanged(string count)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(count));
}
}
I add this class in Xaml according to the tutorial on the Xamarin <ContentPage.BindingContext>
<local:UserInventory />
</ContentPage.BindingContext>
So the variables are bounded correctly and I have no issues seeing updates on the current page or when I push new pages. The issue is when I swipe back on iOS the previous screen with the button "Current Inventory: 35" does not update to reflect the new changes. If I push that screen the changes are reflected.
Is there anyway to ensure the bounded data is updated when you go back (PopAsync()) ?
Try overriding page's OnAppearing() method and call OnPropertyChanged from there.
Assuming 'UserInventory' the binded VM.....
public partial class Page1:ContentPage
{
public Page1()
{
InitializeComponent();
VM = (UserInventory)BindingContext;
}
public UserInventory VM { get; }
protected override void OnAppearing()
{
VM.Notify();
base.OnAppearing();
}
}
.
public class UserInventory: INotifyPropertyChanged
{
........
public void Notify()
{
OnPropertyChanged(nameof(Count));
}
}

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?

Cannot add a null child view to a ViewGroup

I'm trying an exercise to list an get the sensors info, but I'm getting the titles error, and i have no much info about it, could somebody help me?
The exercise says:
"This activity uses the layout created from XML, which basically is an empty linearlayout. From Java we access to this layout with the object "raiz". To the layout we will be adding a series of views according to the sensors found in the device. For each sensor is added: a TextView with the name of the sensor; a LinearLayout of horizontal type to contain: a TextView with "X" and the sensor value on this axis; a TextView with the value of the sensor in "Y"; a TextView with the value of teh axis "Z", if applies. The references to the TextView where the values ​​of the sensors are displayed are stored in aTextView [] [] array, where the first index identifies the sensor number and the second the dimension, X, Y, Z.
The onSensorChanged() method makes a loop to locate the sensor index that has changed and the TextViews corresponding to the sensor are modified with the values ​​read"
Here is my layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/raiz"
android:orientation="vertical">
</LinearLayout>
Here is my activity main
package com.example.familia.sensores2;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.List;
public class MainActivity extends AppCompatActivity implements SensorEventListener {
private List<Sensor> listaSensores;
private TextView aTextView[][] = new TextView[20][3];
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout raiz = (LinearLayout) findViewById(R.id.raiz);
SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
listaSensores = sm.getSensorList(Sensor.TYPE_ALL);
int n = 0;
for(Sensor sensor:listaSensores){
TextView mTextView = new TextView(this);
mTextView.setText(sensor.getName());
raiz.addView(mTextView);
LinearLayout nLinearLayout = new LinearLayout(this);
raiz.addView(nLinearLayout);
for(int i = 0; i<2; i++){
aTextView[n][i] = new TextView(this);
aTextView[n][i].setText("?");
aTextView[n][i].setWidth(87);
}
TextView xTextView = new TextView(this);
xTextView.setText(" X: ");
nLinearLayout.addView(xTextView);
nLinearLayout.addView(aTextView[n][0]);
TextView yTextView = new TextView(this);
yTextView.setText(" Y: ");
nLinearLayout.addView(yTextView);
nLinearLayout.addView(aTextView[n][1]);
TextView zTextView = new TextView(this);
zTextView.setText(" Z: ");
nLinearLayout.addView(zTextView);
nLinearLayout.addView(aTextView[n][2]);
sm.registerListener(this, sensor, SensorManager.SENSOR_DELAY_UI);
n++;
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy){}
#Override
public void onSensorChanged(SensorEvent event){
synchronized (this){
int n = 0;
for(Sensor sensor: listaSensores){
if(event.sensor==sensor){
for(int i=0; i<event.values.length; i++){
aTextView[n][i].setText(Float.toString(event.values[i]));
}
}
n++;
}
}
}}
here my error log
06-05 03:10:17.523 17031-17031/com.example.familia.sensores2 E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.familia.sensores2, PID: 17031
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.familia.sensores2/com.example.familia.sensores2.MainActivity}: java.lang.IllegalArgumentException: Cannot add a null child view to a ViewGroup
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2778)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Caused by: java.lang.IllegalArgumentException: Cannot add a null child view to a ViewGroup
at android.view.ViewGroup.addView(ViewGroup.java:4699)
at android.view.ViewGroup.addView(ViewGroup.java:4681)
at com.example.familia.sensores2.MainActivity.onCreate(MainActivity.java:49)
at android.app.Activity.performCreate(Activity.java:7009)
at android.app.Activity.performCreate(Activity.java:7000)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2731)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856) 
at android.app.ActivityThread.-wrap11(Unknown Source:0) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589) 
at android.os.Handler.dispatchMessage(Handler.java:106) 
at android.os.Looper.loop(Looper.java:164) 
at android.app.ActivityThread.main(ActivityThread.java:6494) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 
Here the full exercise code
Master Android
well after a while looking and reading a lot, i found the error, or errors...
first on the lines:
for(int i = 0; i<2; i++){
aTextView[n][i] = new TextView(this);
aTextView[n][i].setText("?");
aTextView[n][i].setWidth(87);
}
i used the value "i<2" when on the exercise says "i<3" this because when i made the exercise for the first time i was getting an error for an "index out of bounds", so i thought that the error was on this line so i changed de value of "3" to "2" and was when i started to get the error of the post (Cannot add a null child view to a ViewGroup).
anyway, that error was because on the lines:
TextView zTextView = new TextView(this);
zTextView.setText(" Z: ");
nLinearLayout.addView(zTextView);
nLinearLayout.addView(aTextView[n][2]);
the line:
nLinearLayout.addView(aTextView[n][2]);
was sending a null value and that caused the error of "Cannot add a null child view to a ViewGroup".
then when i returned the value to "3" i was getting the error of "index out of bounds", so i focused on the next lines:
for(Sensor sensor: listaSensores){
if(event.sensor==sensor){
for(int i = 0; i <event.values.length; i++){
aTextView[n][i].setText(Float.toString(event.values[i]));
}
}
n++;
}
and the error was because the matrix defined on aTextView was for 20x3 but the "event.values.length" returned values greater than 3, why?, i don’t know... I’m not sure if it depends on the sensor type or what, so i added a restriction there and adjusted the lines to:
for(Sensor sensor: listaSensores){
if(event.sensor==sensor){
for(int i = 0; i <event.values.length && i <3 ; i++){
aTextView[n][i].setText(Float.toString(event.values[i]));
}
}
n++;
}
and voila... the program runs.... finally i adjusted the activity main layout to:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/raiz"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
this because on my mobile i got a lot of sensors and i can’t view all, so i added a ScrollView.
now with the program running the values changes very fast and it’s impossible to read it, no matter if i modifies the value of "SensorManager.SENSOR_DELAY_UI" to, "SensorManager.SENSOR_DELAY_NORMAL" or other... so I’m looking now how to make these changes slower, but the topic of this post can be closed, i hope this help others....

display of image requires two clics instead of one

A piece of code I created to open an image requires that I click twice on the words to open the image. I don't understand why it doesn't respond to just one click.
In the layout here is the TextView
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="The New Yorker"
android:id="#+id/the_new_yorker"
android:textStyle="italic"
android:textColor="#color/blue"
android:onClick="new_yorker_cartoon"/>
and here is the corresponding function in the java file
public void new_yorker_cartoon(View view) {
final TextView TextGenerique = (TextView) findViewById(R.id.the_new_yorker);
View.OnClickListener monEcouteur = new View.OnClickListener() {
#Override
public void onClick(View v) {
String imageUrl = "http://www.lapasserelle.com/english/l09/imgs/new_yorker.gif";
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(imageUrl), "image/gif");
startActivity(intent);
}
};
TextGenerique.setOnClickListener(monEcouteur);
}
What I don't understand is why I need to click twice on the words New Yorker to display the image new_yorker.gif on my phone.
It might be that the first click is to activate the screen and the second to get the response.

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.

Resources