Listpicker error SelectedItem must always be set t

2020-02-09 10:06发布

问题:

I have a page in a Windows Phone 7 app where the user can edit or delete an Transaction object. The Transaction object is an Linq-to-Sql class that have a relationship with the Account class and the Category class. In the page, I use a ListPicker to let the user select the account and category for the given transaction, like this:

<toolkit:ListPicker Grid.Row="1" FullModeHeader="Choose the Account" FullModeItemTemplate="{StaticResource FullModeItemTemplate}" ExpansionMode="FullScreenOnly" Background="#26000000" Margin="10,0,10,0" Name="Account" SelectedItem="{Binding Account, Mode=TwoWay}" Tap="ListPicker_Tap" />

<toolkit:ListPicker Grid.Row="7" FullModeHeader="Choose the Category" FullModeItemTemplate="{StaticResource FullModeItemTemplate}" ExpansionMode="FullScreenOnly" Background="#26000000" Margin="10,0,10,0" Name="Category" SelectedItem="{Binding Category, Mode=TwoWay}" Tap="ListPicker_Tap" />

The ListPicker_Tap event is a fix for a bug in the Aug/2011 version of the WPF Toolkit for Windows Phone and is simply this:

    private void ListPicker_Tap(object sender, System.Windows.Input.GestureEventArgs e)
    {
        ListPicker lp = (ListPicker)sender; 
        lp.Open();
    }

If the user edit the transaction, everything is fine, but if the user try to delete it, I get an error saying that "SelectedItem must always be set to a valid value".

Here's the code if the user click in the delete button in the appbar in the TransactionPage.xaml.cs:

    private void appBarDelete_Click(object sender, EventArgs e)
    {
        MessageBoxResult result = MessageBox.Show("Are you sure?\n", "Confirm", MessageBoxButton.OKCancel);

        if (result == MessageBoxResult.OK)
        {
            App.ViewModel.DeleteTransaction(transaction);
        }

        NavigationService.GoBack();
    }

My ViewModel.DeleteTransaction method:

    public void DeleteTransaction(Transaction transaction)
    {
        AllTransactions.Remove(transaction);
        transactionRepository.Delete(transaction);
    }

My transactionRepository.Delete method:

    public void Delete(Transaction transaction)
    {
        Context.Transactions.DeleteOnSubmit(transaction);
        Context.SubmitChanges();
    }

I receive the error in the Context.SubmitChanges() execution, the debug points to the NotifyPropertyChanged inside the Transaction class, the line where I get the error is this:

    protected virtual void SendPropertyChanged(String propertyName)
    {
        if ((this.PropertyChanged != null))
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

In the propertyName attribute the value is "Category". It looks like when deleting the object send the propertychanged event of category and accounts, and because the listpicker is in the TwoWay mode, it have some trouble dealing with it. How could I fix it? I need some help.

回答1:

The problem is that the ListPicker is expecting the SelectedItem to be a ListPickerItem whereas you're binding it to an object of type Transaction. You can get around the problem by binding to the SelectedIndex property instead and then select the appropriate object from your ViewModel based on the index.

Also, if the reason you have the Tap handler defined is because of the bug where the ListPicker does not open when placed within a ScrollViewer, take a look at patch ID 10247. If you recompile the toolkit with that patch it fixes the problem.



回答2:

This error may also be caused by the order of the XAML properties:

This does NOT work (throws the exception because the ItemsSource is null when the SelectedItem is set):

<toolkit:ListPicker DisplayMemberPath="Title" SelectionMode="Single" 
SelectedItem="{Binding SelectedCategory, Mode=TwoWay}"
ItemsSource="{Binding Categories}" />

This works as the itemssource is initialized first:

<toolkit:ListPicker DisplayMemberPath="Title" SelectionMode="Single" 
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory, Mode=TwoWay}" />


回答3:

ListPicker uses Items.IndexOf to get the index of item instance that it should select.

If the instance does not match (it is not an object instance from the collection) the IndexOf will return -1 and the InvalidOperationException is thrown with the message: "SelectedItem must always be set to a valid value".

Override Equals method of the item type in the collection and it will work as expected.

Example:

public override bool Equals(object obj)
{
         var target = obj as ThisType;
         if (target == null)
             return false;

         if (this.ID == target.ID)
             return true;

         return false;
 }

Hope it helps



回答4:

There are just two checks which throws the InvalidOperationException on SelectedItem

  1. Listpicker Items is null (Declarative: Order of attributes matters. if selecteditem must appear after itemsource (Programatic: make sure itemsource is loaded)
  2. Listpicker applies Indexof on Items to set selected item. So make sure you override Equals if necessary.

Debugging with watch on listpicker.Items and overridden Equals method will help us identify issue