Remove ListView item by clicking button on the item

I'm having a bit of a struggle trying to remove the correct item from my ListView. I have a ListView where the items have their own buttons, and when you click on a button on a specific item, then it should remove this specific item. I can sometimes make it work when only removing one item, but when I try to remove the next item, some funky stuff can happen (e.g. removing more than one item or removing the wrong item). I think I know the source of this trouble, but I don't know how else to do it.
I set up the item layout in the getView method in my BaseAdapter:

    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        View view = convertView;
        IInvitation invite = this [position];

        if (view == null) // no view to re-use, create new
            view = context.LayoutInflater.Inflate(Resource.Layout.InvitationLayoutItem, null);

        view.FindViewById<TextView>(Resource.Id.headingInvite).Text = invite.GroupName; //InviteItem.Heading
        view.FindViewById<TextView>(Resource.Id.subheadingInvite).Text = invite.InviterName;

        ImageButton declineButton = view.FindViewById<ImageButton>(Resource.Id.imageButtonDecline);
        declineButton.Click += (object sender, EventArgs e) => 
            {
                invites.Remove (invite);
                Console.WriteLine ("You declined the invite for " + invite.GroupName + " from " + invite.InviterName);
                this.NotifyDataSetChanged ();
            };

        return view;
    }

I believe the problem starts at the declineButton.Click line. Also the invites.Remove method is probably not the right way to remove the correct item, but I've also tried removing the correct item based on the position, but that creates problems when my ListView is very long.

So how do I create an EventHandler correctly for buttons in a ListView item, so I can remove the correct item from my List and view? And is it going to work on a ListView with many items?

Best Answer

Answers

  • kizanlikkizanlik TRMember ✭✭

    As far as I know .NotifyDataSetChanged () needs to be called from UI Thread. So, could you change

    this.NotifyDataSetChanged ();
    

    to

    RunOnUiThread (() => this.NotifyDataSetChanged ());
    

    Please note that RunOnUiThread () method is one of Activity class methods. You may need to pass the current activity to the adapter.

  • Rene4100Rene4100 DKMember ✭✭

    I think you're right! Thanks for the heads-up.

  • KeyzerSKeyzerS NOMember ✭✭
    edited November 2014

    Hey Rene!

    I have struggled with some of the same stuff where each item in my list had a button that would reset the underlying item without the user having to open it by clicking the list item itself. My issues, in addition to struggling a lot with memory because of imageviews for each item, with the button were mostly how I built the adapter itself. The way it initially was set up reused the view, and this caused it to call the wrong items. I struggled a lot with it all.

    I ended up making a view holder for all the parts of the item in the adapter. In the holder I set up my eventhandler like this:

     holder.buttonReset.Click += (object sender, EventArgs e) => {
                int pos = (int) (((Button) sender).GetTag(Resource.Id.buttonReset));
                var currentItem2 = this[pos];
     }
    

    Then I just made sure every created button also had its own tag that matched the list, like this:

         holder.buttonReset.SetTag (Resource.Id.buttonReset, position);
    

    "position" is the int from the GetView, which is the same used to determine the current item. The same you use here:

          IInvitation invite = this [position];
    

    And it works like a charm. I have tested it with around a 100 items in the list, and it works well. I am just a hobbyist, and I am sure there are a lot better and more correct ways to do this. But this method solved getting the right item in the right button for me.

    The buttonclicked then called a method in the fragment which loaded and changed the item from a local database. After that I just ran a Activity.RunOnUiThread (() => adapter2.Clear ()); And then re-added the items (the add in the adapter contains a notify datasetchanged). This is probably very hacky, but I wanted to make sure the items matched the underlying data.

    Edit: This GREAT blogpost has more about the ViewHolder: http://blog.xamarin.com/creating-highly-performant-smooth-scrolling-android-listviews/

  • JohnRowseJohnRowse GBMember ✭✭
    edited November 2014

    I would guess you are having the issue due to not removing the previous clickevent. An event handler is an elaborate list of methods to fire. In this case, each time your adpater reuses a view, it is building up the list, and cauing the events to fire as a stack, up to the item you clicked. In your adapter, you should consider something like:

    declineButton.click -= MyClickEvent;
    declineButton.click += MyClickEvent;
    
    void MyClickEvent(object sender, EventArgs e)
    {
            invites.Remove (invite);
                    Console.WriteLine ("You declined the invite for " + invite.GroupName + " from " + invite.InviterName);
                    this.NotifyDataSetChanged ();
    }
    
  • kizanlikkizanlik TRMember ✭✭

    @John:

    I believe that your guess would throw an exception because of empty click event at first run.

    Every button in item has its own click event handler. Because of calling .NotifyDataSetChanged () method, the view recreates items as in first run. So, attaching an event handler like this shouldn't be problem.

  • GustavoEnriquez.9489GustavoEnriquez.9489 USMember
    edited July 2015

    Great Buddy!! thanks

    It also works with** this.NotifyDataSetChanged().**

  • RajatShirkeRajatShirke USMember ✭✭

    I have a button on ListView to delete the respective row of item where data is stored on local database so its quite difficult for me to check where to define the onclick event and call it

    in get view method code goes as follows

  • RajatShirkeRajatShirke USMember ✭✭

    Button btndelete = view.FindViewById(Resource.Id.btndelet);
    btndelete.Click += Btndelete_Click;
    }
    private void Btndelete_Click(object sender, EventArgs e) {
    fondeletecomplete.Invoke(this, new Onbtndeleteargs(int.Parse(dgradetext.Tag.ToString()), dgradetext.Text.ToString(), decimal.Parse(dtinratetext.Text), decimal.Parse(dperkgtext.Text)));
    }

  • RajatShirkeRajatShirke USMember ✭✭

    and on my mainActivity
    myadapter del = new myadapter(this, lstsource);
    del.fondeletecomplete += Del_fondeletecomplete1;
    }
    private void Del_fondeletecomplete1(object sender, Onbtndeleteargs e) {
    Grade obj = new Grade() {
    id = e._eid,
    grade = e._egrade,
    tinrate = e._etinrate,
    perkg = e._eperkg

            };
            db.deletetable(obj);
            Toast.MakeText(this, "Data Removed Sucessfully", ToastLength.Short).Show();
            loaddata();
        }
    

    here I'm passing values of the type which is described in separate class it goes as below

    public class Grade
    {
    [PrimaryKey,AutoIncrement]
    public int id { get; set; }
    public string grade { get; set; }
    public decimal tinrate { get; set; }
    public decimal perkg { get; set; }
    }

    so all kind of suggestions are appreciated

    thanks in advance

  • master_aminmaster_amin Member ✭✭

    hi @KeyzerS i find this post useful but i didnt find the reftrence of IInvitation =this[position] in your adapter

  • master_aminmaster_amin Member ✭✭
    edited March 2018

    .

Sign In or Register to comment.