Программа для добавления/удаления строки к именам файлов в папке. Часть 2.
Теперь рассмотрим процесс реализации класса ViewModel, наследуемого от ViewModelBase. Это абстрактный класс, который содержится в библиотеке MVVM Light. С помощью него реализуется обновление данных в свойствах, привязанных к элементам UI, а так же возможность передачи сообщений между объектами.
Начнем с описания логики работы первой вкладки программы «Добавление к имени файла»
Реализация добавления строки к имени файла.
Во-первых нам нужно связать TextBox’ы с соответствующими свойствами в MainViewModel через алгоритм привязки.
xaml
<!--Путь до папки с файлами--> <TextBox Grid.Column="0" Name="PathToFolderAddString_tb" Text="{Binding PathToFilesForAddString_Renamer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> <!--Строка до имени файла--> <TextBox Name="AddTextBefore_tb" Text="{Binding StringBeforeFileName_Renamer, UpdateSourceTrigger=PropertyChanged}"/> <!--Строка после имени файла--> <TextBox Name="AddTextAfter_tb" Text="{Binding StringAfterFileName_Renamer, UpdateSourceTrigger=PropertyChanged}"/> <!--Строка в имени файла--> <TextBox Name="InsertTextOnPosition_td" Text="{Binding InsertTextOnPosition_Renamer, UpdateSourceTrigger=PropertyChanged}"/>
C#
//Путь до папки в которой переименовываются файл private string _pathToFilesForAddString_Renamer; public string PathToFilesForAddString_Renamer { get { return _pathToFilesForAddString_Renamer; } set { _pathToFilesForAddString_Renamer = value; //Если меняется путь, то автоматическое //слежение за папкой выключается if (AutoWatchingForFolder) { AutoWatchingForFolder = false; } RaisePropertyChanged(nameof(PathToFilesForAddString_Renamer)); } } //Строка которую нужно вставить до имени файла private string _stringBeforeFileName_Renamer = ""; public string StringBeforeFileName_Renamer { get { return _stringBeforeFileName_Renamer; } set { _stringBeforeFileName_Renamer = value; RaisePropertyChanged(nameof(StringBeforeFileName_Renamer)); } } //Строка которую нужно вставить после имени файла private string _stringAfterFileName_Renamer = ""; public string StringAfterFileName_Renamer { get { return _stringAfterFileName_Renamer; } set { _stringAfterFileName_Renamer = value; RaisePropertyChanged(nameof(StringAfterFileName_Renamer)); } } //Строка которую нужно вставить на определенную позицию private string _insertTextOnPosition_Renamer = ""; public string InsertTextOnPosition_Renamer { get { return _insertTextOnPosition_Renamer; } set { _insertTextOnPosition_Renamer = value; RaisePropertyChanged(nameof(InsertTextOnPosition_Renamer)); } }
Так как в WPF нет элемента NumericUpDown, пришлось его искать в сторонних библиотеках. Выбор пал на Xceed, она есть как платная, так и бесплатная. Подключить ее можно через менеджер пакетов NuGet. Чтобы использовать ее элементы, нам нужно прописать:
xaml
xmlns:xctk=”http://schemas.xceed.com/wpf/xaml/toolkit”
После этого можно создать сам элемент и привязать его к свойству
xaml
<xctk:IntegerUpDown Minimum="0" Margin="10 5 0 5" Text="{Binding InputPosition_Renamer, UpdateSourceTrigger=PropertyChanged}"/>
C#
//Позиция начиная с которой нужно вставить строку private int _inputPosition_Renamer = 0; public int InputPosition_Renamer { get { return _inputPosition_Renamer; } set { _inputPosition_Renamer = value; RaisePropertyChanged(nameof(InputPosition_Renamer)); } }
Помимо технологии привязки данных, в WPF предусмотрено связывание действий элементов Button с методами класса ViewModel через систему команд. Для этого существует вспомогательный класс RelayCommand, наследуемый от интерфейса ICommand. Его реализация уже встроена в библиотеку MVVM Light, но его можно реализовать и самостоятельно. (Если нужна будет реализация напишу в комментариях).
Для того чтобы связать команду и визуальный элемент напишем следующее:
xaml
<Button Name="RenameFilesOneTime_btn" Command="{Binding RenameFiles_Renaimer_Command}" Content="Переименовать файлы" />
C#
//Команда для переименовывания файлов private RelayCommand _renameFiles_Renamer_Command; public RelayCommand RenameFiles_Renaimer_Command { get { return _renameFiles_Renamer_Command ?? (_renameFiles_Renamer_Command = new RelayCommand(() => { RenameFilesMethod_Renamer(PathToFilesForAddString_Renamer); })); } }
Метод RenameFilesMethod_Renamer(string pathToFolder) принимает на вход путь до папки, в которой лежат файлы.
//Метод для переименовывания файлов в папке private void RenameFilesMethod_Renamer(string pathToFolder) { if (CheckEnteredPath(pathToFolder) == 0) { return; } //Процесс переименования IEnumerable<FileInfo> filesToRename = Directory.GetFiles(pathToFolder) .Select(f => new FileInfo(f)); foreach (FileInfo fileInfo in filesToRename) { string newFileName = RenameFileMethod_Renamer(fileInfo); if (newFileName == "errorExceptionCheckIt") { return; } Messenger.Default.Send(new AddInfoInLogFileMessage() { MessageForLogFile = "Файл " + fileInfo.Name + " переименован в " + newFileName, TextColorInLog = "#000000" }); } }
Метод RenameFileMethod_Renamer(FileInfo inputFile) сделан для удобства, потому что позже будет реализован алгоритм постоянного сканирования папки на наличие новых файлов и этот метод пригодится.
//Метод для переименовывания одного файла private string RenameFileMethod_Renamer(FileInfo inputFile) { try { string newFileName; newFileName = $@"{StringBeforeFileName_Renamer}{ Path.GetFileNameWithoutExtension(inputFile.Name).Insert(InputPosition_Renamer, InsertTextOnPosition_Renamer) }" + $@"{StringAfterFileName_Renamer}{inputFile.Extension}"; string newFileFullPath = Path.Combine(inputFile.DirectoryName, newFileName); File.Move(inputFile.FullName, newFileFullPath); return newFileName; } catch (Exception e) { DispatcherHelper.CheckBeginInvokeOnUI(() => { Messenger.Default.Send(new AddInfoInLogFileMessage() { MessageForLogFile = e.Message, TextColorInLog = "#000000" }); }); } return "errorExceptionCheckIt"; }
Метод CheckEnteredPath(string pathToFolder) проверяет, правильный ли путь был передан на вход метода, выполняющего переименование.
private int CheckEnteredPath(string pathToFolder) { //Проверка наличия указанного пут���� if (pathToFolder == "") { Messenger.Default.Send(new AddInfoInLogFileMessage() { MessageForLogFile = "Не задан путь до папки!", TextColorInLog = "#B22222" }); return 0; } //Проверка существования выбранной папаки if (!System.IO.Directory.Exists(pathToFolder)) { Messenger.Default.Send(new AddInfoInLogFileMessage() { MessageForLogFile = "Такой папки не существует!", TextColorInLog = "#B22222" }); return 0; } return 1; }
Чтобы получить список файлов находящихся в выбранной папке, мы используем метод
Directory.GetFiles(pathToFolder)
Который на выход получает путь до папки а возвращает имена файлов в ней.
Далее эти имена мы заносим в строго типизированный перечислитель:
IEnumerable<FileInfo> filesToRename = Directory.GetFiles(pathToFolder).Select(f => new FileInfo(f));
Тип FileInfo предоставляет свойства и методы для работы с файлами.
Если переименование проходит успешно, то мы отправляем сообщение из класса ViewModel в MainWindow.xaml.cs
Код во ViewModel
Messenger.Default.Send(new AddInfoInLogFileMessage() { MessageForLogFile = "Файл " + fileInfo.Name + " переименован в " + newFileName, TextColorInLog = "#000000" });
В конструкторе класса MainWindow.xaml.cs
Messenger.Default.Register<AddInfoInLogFileMessage>(this, x => { LogText_tb.AppendText(DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToLongTimeString() + " " + x.MessageForLogFile + "\n"); LogText_tb.ScrollToEnd(); ScrollViewerForLog.ScrollToBottom(); });
Чтобы отправлять сообщения мы создали вспомогательный класс AddInfoInLogFileMessage в папке Helpers. Он содержит в себе свойства, которые мы можем соотнести с параметрами, передаваемыми через сообщения.
class AddInfoInLogFileMessage { public string MessageForLogFile { get; set; } public string TextColorInLog { get; set; } }
Таким образом был описан алгоритм для добавления строки к именам всех файлов в выбранной папке. Теперь же покажу, как реализовать автоматическое отслеживание появления изменение структуры папки.
Автоматическое переименование файлов в папке.
UI содержит элемент CheckBox, который включает поток для отслеживания изменений в структуре папки (добавление, удаление, переименование файлов в ней)
<CheckBox Margin="0 10 0 10" IsChecked="{Binding AutoWatchingForFolder, UpdateSourceTrigger=PropertyChanged}"> <TextBlock Text="Автоматически переименовывать файлы при появлении в папке" TextWrapping="Wrap"/> <i:Interaction.Triggers> <i:EventTrigger EventName="Checked"> <i:InvokeCommandAction Command="{Binding StartWatchingForFolder_Renaimer_Command}"/> </i:EventTrigger> <i:EventTrigger EventName="Unchecked"> <i:InvokeCommandAction Command="{Binding StopWatchingForFolder_Renaimer_Command}"/> </i:EventTrigger> </i:Interaction.Triggers> </CheckBox>
С помощью библиотеки System.Windows.Interactivity удаётся привязать события элемента UI к командам класса ViewModel.
//Начать отслеживать появление изменений в папке public RelayCommand StartWatchingForFolder_Renaimer_Command { get { return _startWatchingForFolder_Ranaimer_Command ?? (_startWatchingForFolder_Ranaimer_Command = new RelayCommand(() => { //Проверка на наличие пути if (CheckEnteredPath(PathToFilesForAddString_Renamer) == 0) { AutoWatchingForFolder = false; return; } if (watcher == null) { watcher = new FileSystemWatcher(); watcher.Path = PathToFilesForAddString_Renamer; watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.FileName | NotifyFilters.DirectoryName; } watcher.Changed += (sender, args) => { //Отправка сообщения об изменении состояния файла SendMessageInLogFromWatcher(args); }; watcher.Created += (sender, args) => { //Отправка сообщения об изменении состояния файла SendMessageInLogFromWatcher(args); FileInfo inputFile = new FileInfo(args.FullPath); Thread.Sleep(50); if (RenameFileMethod_Renamer(inputFile) == "errorExceptionCheckIt") { return; } }; watcher.Deleted += (sender, args) => { //Отправка сообщения об изменении состояния файла SendMessageInLogFromWatcher(args); }; watcher.Renamed += (sender, args) => { //Отправка сообщения об изменении состояния файла SendMessageInLogFromWatcher(args); }; watcher.EnableRaisingEvents = true; })); } } //Остановить отслеживание изменений в папке public RelayCommand StopWatchingForFolder_Renaimer_Command { get { return _stopWatchingForFolder_Ranaimer_Command ?? (_stopWatchingForFolder_Ranaimer_Command = new RelayCommand(() => { if (watcher!=null) { watcher.EnableRaisingEvents = false; watcher = null; } })); } }
В отслеживании помогает специальный класс FileSystemWatcher, который ожидает поступления уведомления от файловой системы об изменениях и инициализирует при этом события.
После создания экземпляра этого класса его нужно настроить
//Создание экземпляра класса watcher = new FileSystemWatcher(); //Прописываем путь до папки watcher.Path = PathToFilesForAddString_Renamer; //Добавляем необходимые фильтры (В зависимости от них система будет выбирать на какие изменения ей нужно реагировать) watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.FileName | NotifyFilters.DirectoryName;
Дальше нужно подписаться на события, которые возникают при изменениях в каталоге.
//Событие создания файла в каталоге. Самое нужное нам событие. watcher.Created += (sender, args) => { //Отправка сообщения об изменении состояния файла SendMessageInLogFromWatcher(args); FileInfo inputFile = new FileInfo(args.FullPath); Thread.Sleep(50); if (RenameFileMethod_Renamer(inputFile) == "errorExceptionCheckIt") { return; } };
Так же здесь есть метод SendMessageInLogFromWatcher() который в качестве входного параметра получает экземпляр класса FileInfo и выводит в окно логов информацию о проведенной операции.
//Отправка сообщения об изменении состояния файла public void SendMessageInLogFromWatcher(FileSystemEventArgs args) { if (args is RenamedEventArgs) { DispatcherHelper.CheckBeginInvokeOnUI(() => { Messenger.Default.Send(new AddInfoInLogFileMessage() { MessageForLogFile = "Файл: " + (args as RenamedEventArgs).OldName + " был переименован в " + args.Name, TextColorInLog = "#000000" }); }); return; } DispatcherHelper.CheckBeginInvokeOnUI(() => { Messenger.Default.Send(new AddInfoInLogFileMessage() { MessageForLogFile = "Файл: " + args.Name + " был " + args.ChangeType, TextColorInLog = "#000000" }); }); }
Класс DispatcherHelper предоставляет возмодность отправлять информацию в виде сообщений между потоками.
Можно сказать, что реализация автоматического добавления строки к названию файла, который появляется в папке, завершена.
Удаление строки из названия файла.
Эта часть программы очень похожа на процесс добавления строк, поэтому описывать ее второй раз не вижу смысла. И так получился большой пост, перегруженный большим количеством кода. Как удалять строки можно посмотреть в исходном коде программы.
Итоги
Подведем маленький итог. Было создано простенькое приложение, которое изменяет имена всех файлов в выбранной папке. Удалось даже прикрутитьреализовать паттерн MVVM, такая архитектура пригодится в случае расширения возможностей программы.
Спасибо за внимание. По возникающим вопросам пишите в комментарии.
И, как обещал ссылка проекта на GitHub.