Removing treeviewitem using MVVM pattern in WPF application

bnolor

New member
Joined
Mar 31, 2021
Messages
4
Programming Experience
Beginner
I'm making a file explorer app using MVVM pattern in C# WPF. Right now I want to implement watcher events that are responsible for adding, removing and renaming items from treeview. I already have adding, and partially renaming (I think that in this case I have to combine deleting and adding). I'm struggling with deletion.

Deleted files are still in the treeview. Here's an app window. For example, Folder 2 shouldn't exist because I deleted it.
1617210840037.png


I'm facing a deadline to hand this over and I would very much appreciate any kind of help. Here's a code I've written in class DirectoryInfoViewModel

DirectoryInfoViewModel.cs:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;



namespace ViewModels

{

    public class DirectoryInfoViewModel : FileSystemInfoViewModel

    {

        public ObservableCollection<FileSystemInfoViewModel> Items { get; private set; }
= new ObservableCollection<FileSystemInfoViewModel>();



        public bool Open(string path)
        {
            bool result = false;

            try
            {
                 FileSystemWatcher Watcher = new FileSystemWatcher(path);
                Watcher.Created += OnFileCreated;
                Watcher.Renamed += OnFileRenamed;
                Watcher.Deleted += OnFileDeleted;
                //Watcher.Changed += OnFileChanged;
                Watcher.Error += Watcher_Error;
                Watcher.EnableRaisingEvents = true;

                foreach (var dirName in Directory.GetDirectories(path))
                {
                    var dirInfo = new DirectoryInfo(dirName);
                    DirectoryInfoViewModel itemViewModel = new DirectoryInfoViewModel
                    {
                        Model = dirInfo
                    };
                    itemViewModel.Open(dirName);
                    Items.Add(itemViewModel);
                }

                foreach (var fileName in Directory.GetFiles(path))
                {
                    var fileInfo = new FileInfo(fileName);
                    FileInfoViewModel itemViewModel = new FileInfoViewModel();
                    itemViewModel.Model = fileInfo;
                    Items.Add(itemViewModel);
                }

                result = true;

            }

            catch (Exception ex)
            {
                Exception = ex;
            }

            return result;
        }


        public Exception Exception { get; private set; }

        private static void Watcher_Error(object sender, ErrorEventArgs e) =>
            System.Windows.MessageBox.Show(e.GetException().ToString());

        public void OnFileCreated(object sender, FileSystemEventArgs e)
        {
            System.Windows.Application.Current.Dispatcher.Invoke(() => OnFileCreated(e));
        }

        private void OnFileCreated(FileSystemEventArgs e)
        {
            Debug.WriteLine("File Created: " + e.Name);
            if (!Items.Any(x => x.Caption == e.Name))

            {

                var dirInfo = new DirectoryInfo(e.FullPath);
                DirectoryInfoViewModel itemViewModel = new DirectoryInfoViewModel();
                itemViewModel.Model = dirInfo;
                Items.Add(itemViewModel);

            }

        }


        public void OnFileDeleted(object sender, FileSystemEventArgs e)
        {
            System.Windows.Application.Current.Dispatcher.Invoke(() => OnFileDeleted(e));
        }

        private void OnFileDeleted(FileSystemEventArgs e)
        {


                Debug.WriteLine("File Deleted: " + e.Name);
            if (Items.Any(x => x.Caption == e.Name))
            {
                var dirInfo = new DirectoryInfo(e.FullPath);
                Debug.WriteLine("File path: " + e.FullPath);
                DirectoryInfoViewModel itemViewModel = new DirectoryInfoViewModel();

                itemViewModel.Model = dirInfo;
                Items.Remove(itemViewModel);         
            }

        }

        public void OnFileRenamed(object sender, FileSystemEventArgs e)
        {
            System.Windows.Application.Current.Dispatcher.Invoke(() => OnFileRenamed(e));
        }

        private void OnFileRenamed(FileSystemEventArgs e)

        {
            Debug.WriteLine("File Renamed: " + e.Name);
            OnFileDeleted(e);
            OnFileCreated(e); 
        }

    }

}
 
Last edited:
Solution
This is not a WPF or MVVM issue. This is a basic object oriented programming issue.

Let imagine this scenario where there is a list of fruits:
C#:
class Fruit
{
    public string Name { get; set; }
}

:

var apple = new Fruit() { Name = "Apple" };
var orange = new Fruit() { Name = "Orange" };
var pear = new Fruit() { Name = "Pear" };

var list = new List<Fruit>();
list.Add(apple);
list.Add(orange);
list.Add(pear);

This is what you are currently doing:
C#:
if (list.Any(f => f.Name == "Apple"))
{
    var apple2 = new Fruit() { Name = "Apple" };
    list.Remove(apple2);
}

The Remove() won't find a matching item to remove. That's because apple2 is a different instance than the original apple that was put into...
You are creating a new instance on line 115. That will not match the instance that is already in your observable collection.
 
You are creating a new instance on line 115. That will not match the instance that is already in your observable collection.
Thanks for the answer! Could You be more specific? I'm new to wpf and mvvm and I don't know how to fix it myself
 
This is not a WPF or MVVM issue. This is a basic object oriented programming issue.

Let imagine this scenario where there is a list of fruits:
C#:
class Fruit
{
    public string Name { get; set; }
}

:

var apple = new Fruit() { Name = "Apple" };
var orange = new Fruit() { Name = "Orange" };
var pear = new Fruit() { Name = "Pear" };

var list = new List<Fruit>();
list.Add(apple);
list.Add(orange);
list.Add(pear);

This is what you are currently doing:
C#:
if (list.Any(f => f.Name == "Apple"))
{
    var apple2 = new Fruit() { Name = "Apple" };
    list.Remove(apple2);
}

The Remove() won't find a matching item to remove. That's because apple2 is a different instance than the original apple that was put into the list.

What you want to happen should be:
C#:
if (list.Any(f => f.Name == "Apple"))
{
    list.Remove(apple);
}

As tempting as it is just to give you the answer of how to find the original apple in the list, I'll let you work that out on your own because it will help you reinforce the concept of different instances which may have the same field values.
 
Solution
This is not a WPF or MVVM issue. This is a basic object oriented programming issue.

Let imagine this scenario where there is a list of fruits:
C#:
class Fruit
{
    public string Name { get; set; }
}

:

var apple = new Fruit() { Name = "Apple" };
var orange = new Fruit() { Name = "Orange" };
var pear = new Fruit() { Name = "Pear" };

var list = new List<Fruit>();
list.Add(apple);
list.Add(orange);
list.Add(pear);

This is what you are currently doing:
C#:
if (list.Any(f => f.Name == "Apple"))
{
    var apple2 = new Fruit() { Name = "Apple" };
    list.Remove(apple2);
}

The Remove() won't find a matching item to remove. That's because apple2 is a different instance than the original apple that was put into the list.

What you want to happen should be:
C#:
if (list.Any(f => f.Name == "Apple"))
{
    list.Remove(apple);
}

As tempting as it is just to give you the answer of how to find the original apple in the list, I'll let you work that out on your own because it will help you reinforce the concept of different instances which may have the same field values.
I came up with something like that:
OnFileDeleted:
private void OnFileDeleted(FileSystemEventArgs e)

{

    if (Items.Any(x => x.Caption == e.Name))

    {

        var toDelete = Items.Single(x => x.Caption == e.Name);

        Items.Remove(toDelete);

    }

}
Deletion works, but not quite. When I delete, for example, a single folder everything works perfectly. However, when I try to delete a folder containing other sub-folders I get an error saying "Access denied". Do you have ideas on how to fix it?
 
WHat's the callstack look like? Where is the "Access denied" coming from? Normally for Windows, you'll get an error if you try to delete a non-empty folder.
 
I'm afraid that my callstack is empty after setting a breakpoint at the beginning of OnFileDeleted (case when I try to delete a folder containing other sub-folders and deletion isn't possible to conduct).
There is no problem when I delete a folder containing .txt. (with no other folders). To ilustrate, when I delete Folder 3, everything is ok.
1617309182846.png

However, if I try to delete Folder ('root') or Folder 2, I get the following error (I'm sorry, it's in Polish, but is says that I don't have admin privileges):
1617309331526.png

When I press continue, I end up with (get permission from the user):

1617309377805.png


Additionaly, I get an error from Watcher_Error (access denied):
1617309493859.png

I hope that now I have ilustrated my problem more precisely.
 
Last edited:
I'm not sure, but that call to Watch_Error() looks to be a late notification that the running unelevated user was trying to delete something that they didn't have permissions to. I don't think there is much you can do about it.

This exception has nothing to do with WPF or MVVM. It is an OS level issue and/or .NET issue. You could try looking at the source code for the .NET Framework's implementation of that File and Directory watchers to perhaps get more insight into why the exception is thrown.
 
Back
Top Bottom