Программа для добавления/удаления строки к именам файлов в папке. Часть 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.