1

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 stil in the treeview. For example, Folder shouldn't exist anymore, because I deleted it.

App window

I would very much appreciate help from You. Here's a code I've written in class DirectoryInfoViewModel

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);
     
        }
    } }
Basas
  • 25
  • 6
  • You do not need to use events in your case, it will be enough to implement [INotifyPropertyChanged](https://stackoverflow.com/questions/1315621/implementing-inotifypropertychanged-does-a-better-way-exist) and use it with your observable collection. Also, [do not use events in MVVM at all](https://stackoverflow.com/questions/32669964/where-to-put-events-when-using-mvvm). And [do not use messageboxes too](https://stackoverflow.com/questions/454868/handling-dialogs-in-wpf-with-mvvm). Or use MMVM light, for example – ba-a-aton Mar 31 '21 at 18:03
  • Also, this [question can be helpful for you](https://stackoverflow.com/questions/62303305/add-rename-remove-item-in-treeview-with-mvvm-wpf) – ba-a-aton Mar 31 '21 at 18:08
  • For me events are necessary, because I have to follow my professor's guidelines – Basas Mar 31 '21 at 18:22
  • But it's not even MVVM) If something like that was in your professor's guidelines - he don't understand how it works. I'll recomend you to learn it by yourself. Don't mind his notes. For example - here's good article about real [MVVM pattern](https://intellitect.com/getting-started-model-view-viewmodel-mvvm-pattern-using-windows-presentation-framework-wpf/). – ba-a-aton Mar 31 '21 at 18:35

1 Answers1

2

In private void OnFileDeleted(FileSystemEventArgs e) you create a new DirectoryInfoViewModel and attempt to remove it from the Items collection. ObservableCollection<T>.Remove(T) removes item based on a reference equality check if T does not provide an alternative equality check.

So you attempt to remove an item from for you Items collection which is not in the collection. If you check the return value of Items.Remove(itemViewModel) you will see that it returns false as no item equal to itemViewModel was found in the collection (see Collection.Remove(T)).

This would fix the problem by finding the item you want to remove and then removing it.

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);
    }
}
thabs
  • 603
  • 2
  • 13
  • Thank You very much! You have no idea how much you've helped me! I wish I could upvote your answer, but I don't have enough reputation yet (but I will!) However, there's one issue with this solution. When I delete folders containing other sub-folders I get an error saying 'Access denied'. Do you know how to fix it? – Basas Apr 01 '21 at 13:58
  • You should consider using only one `FileSystemWatcher` for your root folder and set `FileSystemWatcher.IncludeSubdirectories` to `true` (see [here](https://learn.microsoft.com/en-us/dotnet/api/system.io.filesystemwatcher.includesubdirectories?view=net-5.0)). I did a quick test and this fixed the issue. – thabs Apr 02 '21 at 07:13
  • Thank you for reply! Could you elaborate? I set FileSystemWatcher.IncludeSubdirectories to true and have single FileSystemWatcher (at least I think I do), but it didn't change a thing. I'm pretty sure that I'm doing something wrong, because I'm new to this... – Basas Apr 02 '21 at 12:47
  • You will have to change most of your code. Consider this: Before, you had a `FileSystemWatcher` for each directory, so when `OnFileDeleted` was called, you knew that the deleted file/directory was directly contained in the directory your `DirectoryInfoViewModel` represents. But now, you only have a single Watcher at the root level and you will receive all the events at the root level. This means you cannot compare based on your `Caption` anymore. Instead you have to compare full paths. – thabs Apr 02 '21 at 13:09