简体   繁体   English

删除TableView中的行后,如何更新indexPath?

[英]How can I update my indexPath after deleting a Row in a TableView?

I'm new to Xamarin and trying to make some basic todolist iOS app. 我是Xamarin新手,正在尝试制作一些基本的todolist iOS应用。

After struggling for a while with TableViews , I now have a TableSource, and custom cells containing a UISwitch . 在使用TableViews挣扎了一段时间之后,我现在有了一个TableSource和包含UISwitch自定义单元格。

My goal is, when the user activate the UISwitch , the cell is deleted for the TableView . 我的目标是,当用户激活UISwitch ,将为TableView删除单元格。 To do that I created some custom event in my custom cell. 为此,我在自定义单元格中创建了一些自定义事件。

public partial class TodoTableViewCell : UITableViewCell
{
    public delegate void DeleteTodoEventHandler(UITableView tableView, NSIndexPath indexPath);
    public event DeleteTodoEventHandler DeleteTodo;

    partial void DeleteTodoEvent(UISwitch sender)
    {
        if (DeleteTodo != null)
            DeleteTodo(null, new NSIndexPath());
    }

    public TodoTableViewCell(IntPtr p):base(p)
    {

    }

    public TodoTableViewCell(string reuseIdentifier) : base(UITableViewCellStyle.Default, reuseIdentifier)
    {

    }

    public void UpdateCell(TodoItem Item)
    {
        TodoLabel.Text = Item.Name;
        DateLabel.Text = Formating.formatDate(Item.Date);
        HourLabel.Text = Formating.formatTime(Item.Date);
    }
}

DeleteTodoEvent is called when the user activate the UISwitch . 当用户激活UISwitch时,将调用DeleteTodoEvent Here is my tablesource : 这是我的表源:

public class TableSource : UITableViewSource
{

    List<TodoItem> TableItems;
    string CellIdentifier = "TodoCell";

    ...

    public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
    {
        TodoTableViewCell cell = tableView.DequeueReusableCell(CellIdentifier) as TodoTableViewCell;
        TodoItem item = TableItems[indexPath.Row];

        //---- if there are no cells to reuse, create a new one
        if (cell == null)
        { 
            cell = new TodoTableViewCell(CellIdentifier);
        }

        cell.DeleteTodo += (table, index) => DeleteTodoHandler(tableView, indexPath);

        cell.UpdateCell(item);

        return cell;
    }

    ...

    private void DeleteTodoHandler(UITableView tableView, NSIndexPath indexPath)
    {
        TableItems.RemoveAt(indexPath.Row);
        // delete the row from the table
        tableView.DeleteRows(new NSIndexPath[] { indexPath }, UITableViewRowAnimation.Fade);
    }
}

Basically, DeleteTodoHandler is called whenever the user click on the UISwitch . 基本上,只要用户单击UISwitch就会调用DeleteTodoHandler It correctly deletes the cell at first. 首先它会正确删除该单元格。 However, the indexPath is never updated. 但是,indexPath永远不会更新。

By that I mean : Imagine I have 3 cells in my TodoList. 我的意思是:想象一下,我的TodoList中有3个单元格。

Cell1 -> indexPath.Row = 0 Cell2 -> indexPath.Row = 1 Cell3 -> indexPath.Row = 2 Cell1-> indexPath.Row = 0 Cell2-> indexPath.Row = 1 Cell3-> indexPath.Row = 2

If I delete the second one, Cell2, the indexPath from Cell3 should be = to 1 and not 2. This is not the case. 如果删除第二个单元格Cell2,则Cell3的indexPath应该= = 1而不是2。事实并非如此。 Which means that if I try to delete Cell3 after that, 这意味着如果我之后尝试删除Cell3,

TableItems.RemoveAt(indexPath.Row) 

will try to remove the item 3 from the list which is leading us to an exception since there are only 2 items in the list. 会尝试从列表中删除项目3,这导致我们出现异常,因为列表中只有2个项目。

I'm following the examples I found in the Xamarin Doc for the deletion of rows in a TableView but in my case it's obviously not working. 我正在遵循我在Xamarin Doc中找到的用于删除TableView中的行的示例,但就我而言,它显然不起作用。 The indexPath.Row being read-only, what can I do guys to correctly update the indexPath after deleting an item from the TableView ? indexPath.Row是只读的,从TableView中删除项目后,我该怎么做才能正确更新indexPath?

You should use ViewController which include both of the UITableView and you custom UITableSource object to handle the delete event. 您应该使用同时包含UITableView和自定义UITableSource对象的ViewController来处理删除事件。

Then you can get the correct row you clicked by the method from UITableView like this: 然后,您可以从UITableView中获得方法单击的正确行,如下所示:

Foundation.NSIndexPath indexPath = tableView.IndexPathForCell(cell); 

And delete the row's data from your data list and your UI in the same time. 并同时从数据列表和用户界面中删除该行的数据。

This is a sample code I wrote for you, take a look: 这是我为您编写的示例代码,请看一下:

This is the ViewController: 这是ViewController:

public override void ViewDidLoad ()
        {
            //Create a custom table source
            MyTableSource mySource = new MyTableSource ();

            //Create a UITableView
            UITableView tableView = new UITableView ();
            CGRect tbFrame = this.View.Bounds;
            tbFrame.Y += 20;
            tbFrame.Height -= 20;
            tableView.Frame = tbFrame;
            tableView.Source = mySource;
            tableView.RowHeight = 50;
            this.Add (tableView);

            //Handle delete event
            mySource.DeleteCell += (cell) => {
                //Get the correct row
                Foundation.NSIndexPath indexPath = tableView.IndexPathForCell(cell);
                //Remove data from source list
                mySource.RemoveAt(indexPath.Row);
                //Remove selected row from UI
                tableView.BeginUpdates();
                tableView.DeleteRows(new Foundation.NSIndexPath[]{indexPath},UITableViewRowAnimation.Fade);
                tableView.EndUpdates();
            };
        }

This MyTableSource.cs: 此MyTableSource.cs:

public delegate void DeleteCellHandler(UITableViewCell cell);
        public event DeleteCellHandler DeleteCell;

        private string CellID = "MyTableCell";
        private List<string> tableItems;

        public MyTableSource ()
        {
            tableItems = new List<string> ();
            for (int i = 0; i < 10; i++) {
                tableItems.Add ("Test Cell " + i.ToString ());
            }
        }

        public void RemoveAt(int row)
        {
            tableItems.RemoveAt (row);
        }

        #region implement from UITableViewSource

        public override nint RowsInSection (UITableView tableview, nint section)
        {
            return tableItems.Count;
        }

        public override UITableViewCell GetCell (UITableView tableView, Foundation.NSIndexPath indexPath)
        {
            MyTableCell cell = tableView.DequeueReusableCell (CellID) as MyTableCell;
            if (null == cell) {
                //Init custom cell
                cell = new MyTableCell (UITableViewCellStyle.Default, CellID);
                //Bind delete event
                cell.DeleteCell += delegate {
                    if(null != DeleteCell)
                        DeleteCell(cell);
                };
            }
            cell.Title = tableItems [indexPath.Row];
            return cell;
        }

        #endregion

This is MyTableCell.cs: 这是MyTableCell.cs:

public class MyTableCell : UITableViewCell
    {
        public delegate void DeleteCellHandler();
        public event DeleteCellHandler DeleteCell;

        UILabel lbTitle = new UILabel ();
        UIButton btnDelete = new UIButton (UIButtonType.System);

        public string Title{
            get{ 
                return lbTitle.Text;
            }
            set{ 
                lbTitle.Text = value;
            }
        }

        public MyTableCell (UITableViewCellStyle style, string reuseID) : base(style,reuseID)
        {
            lbTitle.Text = "";
            lbTitle.Frame = new CoreGraphics.CGRect (0, 0, 100, 50);
            this.AddSubview (lbTitle);

            btnDelete.SetTitle ("Delete", UIControlState.Normal);
            btnDelete.Frame = new CoreGraphics.CGRect (120, 0, 50, 50);
            this.AddSubview (btnDelete);
            btnDelete.TouchUpInside += delegate {
                if(null != DeleteCell)
                    DeleteCell();
            };
        }
    }

Hope it can help you. 希望它能对您有所帮助。

If you still need some advice, leave me a message here. 如果您仍然需要建议,请在此处给我留言。

------------------------------New------------------------------------ - - - - - - - - - - - - - - - 新 - - - - - - - - - - -----------------

I just suggest you move all logic code into the ViewController, because according to the MVC model, it should in Controller layer. 我只是建议您将所有逻辑代码移到ViewController中,因为根据MVC模型,它应该在Controller层中。

And if you want to manage your data by invoking some API or loading data from your local database, it will be more convenient. 而且,如果您想通过调用一些API或从本地数据库加载数据来管理数据,这将更加方便。

Of cause you can put your delete event in your TableSource, just need to remove the event from controller and put it in TableSource, like: 当然,您可以将delete事件放在TableSource中,只需要从控制器中删除该事件并将其放入TableSource中即可,例如:

ViewController.cs: ViewController.cs:

public override void ViewDidLoad ()
        {
            //Create a custom table source
            MyTableSource mySource = new MyTableSource ();

            //Create a UITableView
            UITableView tableView = new UITableView ();
            CGRect tbFrame = this.View.Bounds;
            tbFrame.Y += 20;
            tbFrame.Height -= 20;
            tableView.Frame = tbFrame;
            tableView.Source = mySource;
            tableView.RowHeight = 50;
            this.Add (tableView);

//          //Handle delete event
//          mySource.DeleteCell += (cell) => {
//              //Get the correct row
//              Foundation.NSIndexPath indexPath = tableView.IndexPathForCell(cell);
//              //Remove data from source list
//              mySource.RemoveAt(indexPath.Row);
//              //Remove selected row from UI
//              tableView.BeginUpdates();
//              tableView.DeleteRows(new Foundation.NSIndexPath[]{indexPath},UITableViewRowAnimation.Fade);
//              tableView.EndUpdates();
//          };
        }

In TableSource.cs, change GetCell method like this one: 在TableSource.cs中,像这样更改GetCell方法:

public override UITableViewCell GetCell (UITableView tableView, Foundation.NSIndexPath indexPath)
        {
            MyTableCell cell = tableView.DequeueReusableCell (CellID) as MyTableCell;
            if (null == cell) {
                //Init custom cell
                cell = new MyTableCell (UITableViewCellStyle.Default, CellID);
                //Bind delete event
                cell.DeleteCell += delegate {
//                  if(null != DeleteCell)
//                      DeleteCell(cell);
                    //Get the correct row
                    Foundation.NSIndexPath dIndexPath = tableView.IndexPathForCell(cell);
                    int deleteRowIndex = dIndexPath.Row;
                    Console.WriteLine("deleteRowIndex = "+deleteRowIndex);
                    //Remove data from source list
                    RemoveAt(deleteRowIndex);
                    //Remove selected row from UI
                    tableView.BeginUpdates();
                    tableView.DeleteRows(new Foundation.NSIndexPath[]{dIndexPath},UITableViewRowAnimation.Fade);
                    tableView.EndUpdates();
                };
            }
            cell.Title = tableItems [indexPath.Row];
            return cell;
        }

The only mistake you made is you should not add any parameter in your custom cell's delete event handle. 您犯的唯一错误是,您不应在自定义单元格的删除事件句柄中添加任何参数。

Because when you delete a cell from your table, the other cells will never update their own IndexPath. 因为当您从表中删除一个单元格时,其他单元格将永远不会更新其自己的IndexPath。

For example, if you have 3 rows, and you delete the second one, it works. 例如,如果您有3行,而您删除了第二行,则它可以工作。 But when you try to delete the third row(It's the second right now,because you just delete the second one), you will get a exception about that you don't have 3 rows or index out of bounds, because this cell's IndexPath is still 2(The correct one is 1). 但是,当您尝试删除第三行时(现在是第二行,因为您只删除了第二行),您将得到一个例外,因为您没有3行或索引越界,因为此单元格的IndexPath是仍然是2(正确的是1)。

So you need calculate the index in your delete event handler using UITableView's method "IndexPathForCell" like I mention above. 因此,您需要使用UITableView的方法“ IndexPathForCell”在删除事件处理程序中计算索引,就像我上面提到的那样。 It will always get the correct index. 它将始终获得正确的索引。

------------------About your new question-------------------- ------------------关于您的新问题------------------

The mistake you made is about delete event init, you must init it when cell is constructed, like I did: 您犯的错误是关于delete事件init的,您必须在构造单元格时将其初始化,就像我所做的那样:

public override UITableViewCell GetCell (UITableView tableView, Foundation.NSIndexPath indexPath)
        {
            MyTableCell cell = tableView.DequeueReusableCell (CellID) as MyTableCell;
            if (null == cell) {
                cell = new MyTableCell (UITableViewCellStyle.Default, CellID);
                //Bind delete event
                cell.DeleteCell += delegate {
                    //Delete stuff            
                };
            }
            cell.Title = tableItems [indexPath.Row];
            return cell;
        }

If you just use it like yours: 如果只是像您一样使用它:

if (null == cell) {
    cell = new MyTableCell (UITableViewCellStyle.Default, CellID);
}
//Bind delete event
cell.DeleteCell += delegate {
    //Delete stuff            
};
cell.Title = tableItems [indexPath.Row];

It will cause duplicated binding problem, because your table cell is reused, so when you try to refresh/insert/delete or even scroll your UITableView, "GetCell" method will be trigged, so when you try to invoke your cell's delete event, there are multiple delegates will be invoked, the first is working, and if you have at least 2 delegates, it will cause the problem you met. 这将导致重复绑定问题,因为表单元已被重用,因此当您尝试刷新/插入/删除甚至滚动UITableView时,将触发“ GetCell”方法,因此当您尝试调用单元的delete事件时,如果将调用多个委托,第一个委托正在工作,并且如果您至少有2个委托,它将导致遇到的问题。

The effect is like this screenshot: 效果类似于此屏幕截图: 错误截图 Reproduce step: 1 Add a new item for tableview; 重现步骤:1为tableview添加一个新项目; 2 Delete the item you just added; 2删除刚添加的项目; 3 Add a new item again; 3再次添加新项目; 4 Delete the item you just added again; 4删除刚添加的项目;

Because there a 2 delegates, so it thrown an exception. 因为那里有2位代表,所以引发了异常。

At last, I have uploaded the complete sample code to my GitHub, download it if you need. 最后,我将完整的示例代码上传到了我的GitHub,如果需要,请下载它。

UITalbViewManagement Sample code UITalbViewManagement示例代码

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在进行数据库操作时更新XAML,然后在完成后再次更新? - How can I update my XAML while a database operation is ongoing and then update it again after it's finished? 我的数据源不支持删除吗? 我该如何激活呢? - Deleting is not supported in my data source? How can i activate that? 在此命令后如何主动更新或在datagridview1中插入行 - how can I active update or insert row in datagridview1 after this commands 使用TableBindingNavigator删除行后,TableAdapter.Update不起作用 - TableAdapter.Update not working after deleting a row using TableBindingNavigator 删除项目后如何更新ListView? - How to update a ListView after deleting an item? 如何更新我的WAMS表? - How can I update my WAMS table? 如何更新列表每一行中的字段的值? - How can I update the value of a field in every row of a list? 删除数据库中的一行后如何重新排序ID - how to re-order IDs after deleting a row in database 如何在devexpress aspxdatagridview中删除行后显示消息? - How to show message after deleting row in devexpress aspxdatagridview? 用户删除后如何从DataGridview获取行数据 - How to get row data from DataGridview after user deleting it
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM