ObservableCollection Bound from ViewModel Does Not

2019-08-01 08:57发布

问题:

I am allowing a user to delete an item via ContextMenu menu item within the view, and I would like the effect to be seen automatically in the view. As of now, the item in the view remains until the app is restarted, in which case it is then no longer visible. This is very confusing to the user who believes the item has been removed completely from the application. My question is, what am I doing wrong that would fix this issue?

App.xaml.cs

//PictureRepository is a class that gathers pictures from IsolatedStorage
public static PictureRepository PictureList
    {
        get
        {
            return PictureRepository.Instance;
        }
    }

PictureRepository.cs

#region Constants

    public const string IsolatedStoragePath = "Pictures";

    #endregion

    #region Fields

    private readonly ObservableCollection<Picture> _pictures = new ObservableCollection<Picture>();

    #endregion

    #region Properties

    public ObservableCollection<Picture> Pictures
    {
        get { return _pictures; }
    }

    #endregion

    #region Singleton Pattern

    private PictureRepository()
    {
        LoadAllPicturesFromIsolatedStorage();
    }

    public static readonly PictureRepository Instance = new PictureRepository();

    #endregion

    /// <summary>        
    /// Saves to local storage
    /// This method gets two parameters: the captured picture instance and the name of the pictures folder in the isolated storage
    /// </summary>
    /// <param name="capturedPicture"></param>
    /// <param name="directory"></param>
    public void SaveToLocalStorage(CapturedPicture capturedPicture, string directory)
    {
        //call IsolatedStorageFile.GetUserStoreForApplication to get an isolated storage file
        var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
        //Call the IsolatedStorageFile.EnsureDirectory extension method located in the Common IsolatedStorageFileExtensions class to confirm that the pictures folder exists.
        isoFile.EnsureDirectory(directory);

        //Combine the pictures folder and captured picture file name and use this path to create a new file 
        string filePath = Path.Combine(directory, capturedPicture.FileName);
        using (var fileStream = isoFile.CreateFile(filePath))
        {
            using (var writer = new BinaryWriter(fileStream))
            {
                capturedPicture.Serialize(writer);
            }
        }
    }

    /// <summary>
    /// To load all saved pictures and add them to the pictures list page
    /// </summary>
    public CapturedPicture LoadFromLocalStorage(string fileName, string directory)
    {
        //To open the file, add a call to the IsolatedStorageFile.GetUserStoreForApplication
        var isoFile = IsolatedStorageFile.GetUserStoreForApplication();

        //Combine the directory and file name
        string filePath = Path.Combine(directory, fileName);
        //use the path to open the picture file from the isolated storage by using the IsolatedStorageFile.OpenFile method
        using (var fileStream = isoFile.OpenFile(filePath, FileMode.Open, FileAccess.Read))
        {
            //create a BinaryReader instance for deserializing the CapturedPicture instance
            using (var reader = new BinaryReader(fileStream))
            {
                var capturedPicture = new CapturedPicture();
                //create a new instance of the type CapturedPicture called CapturedPicture.Deserialize to deserialize the captured picture and return it
                capturedPicture.Deserialize(reader);
                return capturedPicture;
            }
        }
    }

    /// <summary>
    /// To load all the pictures at start time
    /// </summary>
    private void LoadAllPicturesFromIsolatedStorage()
    {
        //add call to the IsolatedStorageFile.GetUserStoreForApplication to open an isolated storage file
        var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
        //Call the IsolatedStorageFile.EnsureDirectory extension method located in the Common IsolatedStorageFileExtensions class to confirm that the pictures folder exists
        isoFile.EnsureDirectory(IsolatedStoragePath);

        //Call the IsolatedStorageFile.GetFileNames using the pictures directory and *.jpg as a filter to get all saved pictures
        var pictureFiles = isoFile.GetFileNames(Path.Combine(IsolatedStoragePath, "*.jpg"));
        //var pictureFiles = isoFile.GetFileNames(Path.Combine(IsolatedStoragePath, ""));

        //Iterate through all the picture files in the list and load each using the LoadFromLocalStorage you created earlier
        foreach (var pictureFile in pictureFiles)
        {
            var picture = LoadFromLocalStorage(pictureFile, IsolatedStoragePath);
            _pictures.Add(picture);
        }
    }

MainPage.xaml.cs

protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        //Recent is the ListBox in the view that is populated from the ObservableCollection
        if (Settings.AscendingSort.Value)
        {
            //Recent.ItemsSource = PictureRepository.Instance.Pictures.OrderBy(x => x.DateTaken);
            Recent.ItemsSource = App.PictureList.Pictures.OrderBy(x => x.DateTaken);
        }
        else
        {
            //Recent.ItemsSource = PictureRepository.Instance.Pictures.OrderByDescending(x => x.DateTaken);
            Recent.ItemsSource = App.PictureList.Pictures.OrderByDescending(x => x.DateTaken);
        }        
    }

//My attempt at deleting the item from both the View and IsolatedStorage
private void deleteContextMenuItem_Click(object sender, RoutedEventArgs e)
    {
        var listBoxItem = ((MenuItem)sender).DataContext as CapturedPicture;
        fileName = listBoxItem.FileName;

        if (fileName != null)
        {
            try
            {
                //Neither of the following remove the item from the View immediately
                App.PictureList.Pictures.Remove(listBoxItem);
                PictureRepository.Instance.Pictures.Remove(listBoxItem);


                    //To open the file, add a call to the IsolatedStorageFile.GetUserStoreForApplication
                    var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
                    //Combine the directory and file name
                    string filePath = Path.Combine(PictureRepository.IsolatedStoragePath, fileName);
                    isoFile.DeleteFile(filePath);
            }
            catch
            {
                MessageBoxResult r = MessageBox.Show("There was an error deleting the image.", "Notice", MessageBoxButton.OK);
                if (r == MessageBoxResult.OK)
                    return;
            }            
        }
    }

As you can see, I am trying to remove the item from the View and IsolatedStorage. As of now, the removal from IsolatedStorage is working but I cannot seem to get the View to update automatically? I have to wait until the application is restarted and the ListBox is repopulated within the OnNavigatedTo event to see any changes.

回答1:

The problem is that you are assigning an IOrderedEnumerable to Recent.ItemSource and not a ObservableCollection.

In LoadAllPicturesFromIsolatedStorage() create a temporary List where you add the loaded data from your file and then create the ObservableCollection.

PictureRepository.cs

#region Constants

public const string IsolatedStoragePath = "Pictures";

#endregion

#region Properties

public ObservableCollection<Picture> Pictures
{
    get; private set;
}

#endregion

#region Singleton Pattern

private PictureRepository()
{
    LoadAllPicturesFromIsolatedStorage();
}

public static readonly PictureRepository Instance = new PictureRepository();

#endregion

/// <summary>        
/// Saves to local storage
/// This method gets two parameters: the captured picture instance and the name of the pictures folder in the isolated storage
/// </summary>
/// <param name="capturedPicture"></param>
/// <param name="directory"></param>
public void SaveToLocalStorage(CapturedPicture capturedPicture, string directory)
{
    //call IsolatedStorageFile.GetUserStoreForApplication to get an isolated storage file
    var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
    //Call the IsolatedStorageFile.EnsureDirectory extension method located in the Common IsolatedStorageFileExtensions class to confirm that the pictures folder exists.
    isoFile.EnsureDirectory(directory);

    //Combine the pictures folder and captured picture file name and use this path to create a new file 
    string filePath = Path.Combine(directory, capturedPicture.FileName);
    using (var fileStream = isoFile.CreateFile(filePath))
    {
        using (var writer = new BinaryWriter(fileStream))
        {
            capturedPicture.Serialize(writer);
        }
    }
}

/// <summary>
/// To load all saved pictures and add them to the pictures list page
/// </summary>
public CapturedPicture LoadFromLocalStorage(string fileName, string directory)
{
    //To open the file, add a call to the IsolatedStorageFile.GetUserStoreForApplication
    var isoFile = IsolatedStorageFile.GetUserStoreForApplication();

    //Combine the directory and file name
    string filePath = Path.Combine(directory, fileName);
    //use the path to open the picture file from the isolated storage by using the IsolatedStorageFile.OpenFile method
    using (var fileStream = isoFile.OpenFile(filePath, FileMode.Open, FileAccess.Read))
    {
        //create a BinaryReader instance for deserializing the CapturedPicture instance
        using (var reader = new BinaryReader(fileStream))
        {
            var capturedPicture = new CapturedPicture();
            //create a new instance of the type CapturedPicture called CapturedPicture.Deserialize to deserialize the captured picture and return it
            capturedPicture.Deserialize(reader);
            return capturedPicture;
        }
    }
}

/// <summary>
/// To load all the pictures at start time
/// </summary>
private void LoadAllPicturesFromIsolatedStorage()
{
    //add call to the IsolatedStorageFile.GetUserStoreForApplication to open an isolated storage file
    var isoFile = IsolatedStorageFile.GetUserStoreForApplication();
    //Call the IsolatedStorageFile.EnsureDirectory extension method located in the Common IsolatedStorageFileExtensions class to confirm that the pictures folder exists
    isoFile.EnsureDirectory(IsolatedStoragePath);

    //Call the IsolatedStorageFile.GetFileNames using the pictures directory and *.jpg as a filter to get all saved pictures
    var pictureFiles = isoFile.GetFileNames(Path.Combine(IsolatedStoragePath, "*.jpg"));
    //var pictureFiles = isoFile.GetFileNames(Path.Combine(IsolatedStoragePath, ""));

    var pictures = new List<Picture>();

    //Iterate through all the picture files in the list and load each using the LoadFromLocalStorage you created earlier
    foreach (var pictureFile in pictureFiles)
    {
        var picture = LoadFromLocalStorage(pictureFile, IsolatedStoragePath);
        pictures.Add(picture);
    }

    Pictures = new ObservableCollection<Picture>(pictures.OrderBy(x => x.DateTaken));
}

On the MainPage.cs change your OnNavigatedTo method to:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    //Recent is the ListBox in the view that is populated from the ObservableCollection
    //Recent.ItemsSource = PictureRepository.Instance.Pictures.OrderBy(x => x.DateTaken);
    Recent.ItemsSource = App.PictureList.Pictures;
}