refactor mvvm, add datacontext

This commit is contained in:
2025-12-10 21:33:06 +05:00
parent b6ddadc6d3
commit 5479da163e
5 changed files with 566 additions and 621 deletions

View File

@@ -8,6 +8,10 @@
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>

View File

@@ -1,77 +1,66 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
namespace CodeContextGenerator.Models
{
public class FileItem : INotifyPropertyChanged
public partial class FileItem : ObservableObject
{
public string Name { get; set; }
public string FullName { get; set; }
public bool IsDirectory { get; set; }
public ObservableCollection<FileItem> Children { get; set; } = new ObservableCollection<FileItem>();
public FileItem Parent { get; set; }
[ObservableProperty]
private string? name;
private bool? _isSelected;
public bool? IsSelected
[ObservableProperty]
private string? fullName;
[ObservableProperty]
private bool isDirectory;
[ObservableProperty]
private bool? isSelected;
[ObservableProperty]
private FileItem? parent;
public ObservableCollection<FileItem> Children { get; } = new ObservableCollection<FileItem>();
/*partial void OnIsSelectedChanged(bool? oldValue, bool? newValue)
{
get => _isSelected;
set
if (newValue.HasValue)
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged();
UpdateParentSelection();
// Если это директория и установлено конкретное значение (не null), применяем ко всем детям
if (IsDirectory && value.HasValue)
{
foreach (var child in Children)
{
child.IsSelected = value.Value;
}
}
}
UpdateChildrenSelection(newValue.Value);
}
}
UpdateParentSelection();
}*/
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
private void UpdateChildrenSelection(bool value)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
if (!IsDirectory) return;
foreach (var child in Children)
{
child.IsSelected = value;
}
}
private void UpdateParentSelection()
{
if (Parent == null) return;
var children = Parent.Children.ToList();
var children = Parent.Children;
var allSelected = children.All(c => c.IsSelected == true);
var noneSelected = children.All(c => c.IsSelected == false);
var hasIndeterminate = children.Any(c => c.IsSelected == null);
// Если есть дети с null - устанавливаем null
bool hasIndeterminate = children.Any(c => c.IsSelected == null);
if (hasIndeterminate)
{
Parent.IsSelected = null;
}
else if (allSelected)
{
Parent.IsSelected = true;
}
else if (noneSelected)
{
Parent.IsSelected = false;
}
else
{
Parent.IsSelected = null;
}
Parent.IsSelected = hasIndeterminate ? null : (allSelected ? true : (noneSelected ? false : null));
Parent.UpdateParentSelection();
}
public void ClearSelection()
{
IsSelected = false;
foreach (var child in Children)
{
child.ClearSelection();
}
}
}
}

View File

@@ -1,522 +1,431 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
using Microsoft.Win32;
using CodeContextGenerator.Models;
using CodeContextGenerator.Models;
using CodeContextGenerator.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Win32;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace CodeContextGenerator.ViewModels
namespace CodeContextGenerator.ViewModels;
public partial class MainViewModel : ObservableObject, INotifyPropertyChanged
{
public class MainViewModel : INotifyPropertyChanged
[ObservableProperty]
private FileItem? _rootDirectory = new();
[ObservableProperty]
private string _selectedProjectFilePath = string.Empty;
[ObservableProperty]
private bool _isProcessing = false;
[ObservableProperty]
private int _progressValue;
[ObservableProperty]
private string _progressText = string.Empty;
[ObservableProperty]
private string? _lastProjectPath = string.Empty;
[ObservableProperty]
private bool _isProjectLoaded;
private CancellationTokenSource _cancellationTokenSource = new();
public bool CanSelectFolder => !IsProcessing;
public bool CanGenerate => !IsProcessing && IsProjectLoaded && HasSelectedFiles(RootDirectory);
//public ICommand SelectProjectFileCommand { get; }
//public ICommand GenerateCommand { get; }
//public ICommand CancelCommand { get; }
//public ICommand ExitCommand { get; }
public MainViewModel()
{
private FileItem _rootDirectory;
private string _selectedProjectFilePath;
private bool _isProcessing;
private int _progressValue;
private string _progressText;
private CancellationTokenSource _cancellationTokenSource;
private string _lastProjectPath;
private bool _isProjectLoaded;
//ExitCommand = new RelayCommand(_ => Application.Current.Shutdown());
LoadSettings();
}
public FileItem RootDirectory
private bool HasSelectedFiles(FileItem item)
{
if (item == null) return false;
if (item.IsSelected == true && !item.IsDirectory) return true;
return item.Children.Any(HasSelectedFiles);
}
//[RelayCommand(CanExecute = nameof(CanSelectFolder))]
[RelayCommand(CanExecute = nameof(CanSelectFolder))]
private void Exit()
{
Application.Current.Shutdown();
}
//[RelayCommand(CanExecute = nameof(CanSelectFolder))]
[RelayCommand(CanExecute = nameof(CanGenerate))]
private void Cancel()
{
CancelProcessing();
}
[RelayCommand(CanExecute = nameof(CanSelectFolder))]
private async Task SelectProjectFileAsync()
{
var dialog = new OpenFileDialog
{
get => _rootDirectory;
set
Title = "Выберите файл решения или проекта",
CheckFileExists = true,
CheckPathExists = true,
Filter = "Файлы решений (*.sln)|*.sln|Файлы проектов (*.csproj)|*.csproj|Все поддерживаемые (*.sln;*.csproj)|*.sln;*.csproj|Все файлы (*.*)|*.*",
InitialDirectory = !string.IsNullOrEmpty(LastProjectPath) && Directory.Exists(LastProjectPath) ? LastProjectPath : null
};
if (dialog.ShowDialog() != true) return;
SelectedProjectFilePath = dialog.FileName;
LastProjectPath = Path.GetDirectoryName(SelectedProjectFilePath);
SaveSettings();
IsProjectLoaded = false;
await LoadProjectAsync(SelectedProjectFilePath);
}
private async Task LoadProjectAsync(string projectFilePath)
{
IsProcessing = true;
ProgressText = "Загрузка проекта...";
ProgressValue = 0;
try
{
string? projectDirectory = Path.GetDirectoryName(projectFilePath);
var progress = new Progress<int>(value =>
{
_rootDirectory = value;
OnPropertyChanged();
ProgressValue = value;
ProgressText = $"Загрузка: {value}%";
});
_cancellationTokenSource = new CancellationTokenSource();
if (projectFilePath.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))
{
await LoadSolutionAsync(projectFilePath, progress, _cancellationTokenSource.Token);
}
}
public string SelectedProjectFilePath
{
get => _selectedProjectFilePath;
set
else if (projectFilePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase))
{
_selectedProjectFilePath = value;
OnPropertyChanged();
}
}
public bool IsProcessing
{
get => _isProcessing;
set
{
_isProcessing = value;
OnPropertyChanged();
OnPropertyChanged(nameof(CanSelectFolder));
OnPropertyChanged(nameof(CanGenerate));
}
}
public int ProgressValue
{
get => _progressValue;
set
{
_progressValue = value;
OnPropertyChanged();
}
}
public string ProgressText
{
get => _progressText;
set
{
_progressText = value;
OnPropertyChanged();
}
}
public bool IsProjectLoaded
{
get => _isProjectLoaded;
set
{
_isProjectLoaded = value;
OnPropertyChanged();
}
}
public bool CanSelectFolder => !IsProcessing;
public bool CanGenerate => !IsProcessing && IsProjectLoaded && HasSelectedFiles(RootDirectory);
public ICommand SelectProjectFileCommand { get; }
public ICommand GenerateCommand { get; }
public ICommand CancelCommand { get; }
public ICommand ExitCommand { get; }
public MainViewModel()
{
SelectProjectFileCommand = new RelayCommand(SelectProjectFileAsync);
GenerateCommand = new RelayCommand(GenerateFileAsync, _ => CanGenerate);
CancelCommand = new RelayCommand(CancelProcessing, _ => IsProcessing);
ExitCommand = new RelayCommand(_ => Application.Current.Shutdown());
LoadSettings();
}
private bool HasSelectedFiles(FileItem item)
{
if (item == null) return false;
if (item.IsSelected == true && !item.IsDirectory)
return true;
foreach (var child in item.Children)
{
if (HasSelectedFiles(child))
return true;
await LoadProjectDirectoryAsync(projectDirectory, progress, _cancellationTokenSource.Token);
}
return false;
IsProjectLoaded = true;
ProgressText = "Проект загружен успешно";
}
private async void SelectProjectFileAsync(object parameter)
catch (OperationCanceledException)
{
if (!CanSelectFolder) return;
ProgressText = "Загрузка отменена";
RootDirectory = null;
IsProjectLoaded = false;
}
catch (Exception ex)
{
MessageBox.Show($"Ошибка при загрузке проекта: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
RootDirectory = null;
IsProjectLoaded = false;
}
finally
{
IsProcessing = false;
}
}
var dialog = new OpenFileDialog
private async Task LoadSolutionAsync(string solutionPath, IProgress<int> progress, CancellationToken cancellationToken)
{
try
{
string? solutionDirectory = Path.GetDirectoryName(solutionPath);
var solutionProjects = ParseSolutionProjects(solutionPath);
var solutionItem = new FileItem
{
Title = "Выберите файл решения или проекта",
CheckFileExists = true,
CheckPathExists = true,
Filter = "Файлы решений Visual Studio (*.sln)|*.sln|Файлы проектов C# (*.csproj)|*.csproj|Все поддерживаемые файлы (*.sln;*.csproj)|*.sln;*.csproj|Все файлы (*.*)|*.*",
Multiselect = false
Name = Path.GetFileName(solutionPath),
FullName = solutionPath,
IsDirectory = true,
IsSelected = false
};
if (!string.IsNullOrEmpty(_lastProjectPath) && Directory.Exists(_lastProjectPath))
{
dialog.InitialDirectory = _lastProjectPath;
}
int totalProjects = solutionProjects.Count;
int processedProjects = 0;
bool? result = dialog.ShowDialog();
if (result == true)
{
string selectedFilePath = dialog.FileName;
string projectDirectory = Path.GetDirectoryName(selectedFilePath);
if (!string.IsNullOrEmpty(projectDirectory))
{
_lastProjectPath = projectDirectory;
SaveSettings();
SelectedProjectFilePath = selectedFilePath;
IsProjectLoaded = false;
await LoadProjectAsync(selectedFilePath);
}
}
}
private async Task LoadProjectAsync(string projectFilePath)
{
IsProcessing = true;
ProgressText = "Загрузка проекта...";
ProgressValue = 0;
try
{
string projectDirectory = Path.GetDirectoryName(projectFilePath);
SelectedProjectFilePath = projectFilePath;
var progress = new Progress<int>(value =>
{
ProgressValue = value;
ProgressText = $"Загрузка: {value}%";
});
_cancellationTokenSource = new CancellationTokenSource();
// Определяем тип файла
if (projectFilePath.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))
{
await LoadSolutionAsync(projectFilePath, progress, _cancellationTokenSource.Token);
}
else if (projectFilePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase))
{
await LoadProjectDirectoryAsync(projectDirectory, progress, _cancellationTokenSource.Token);
}
IsProjectLoaded = true;
ProgressText = "Проект загружен успешно";
}
catch (OperationCanceledException)
{
ProgressText = "Загрузка отменена";
RootDirectory = null;
IsProjectLoaded = false;
}
catch (Exception ex)
{
MessageBox.Show($"Ошибка при загрузке проекта: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
RootDirectory = null;
IsProjectLoaded = false;
}
finally
{
IsProcessing = false;
}
}
private async Task LoadSolutionAsync(string solutionPath, IProgress<int> progress, CancellationToken cancellationToken)
{
try
{
string solutionDirectory = Path.GetDirectoryName(solutionPath);
var solutionProjects = ParseSolutionProjects(solutionPath);
var solutionItem = new FileItem
{
Name = Path.GetFileName(solutionPath),
FullName = solutionPath,
IsDirectory = true,
IsSelected = false
};
int totalProjects = solutionProjects.Count;
int processedProjects = 0;
foreach (var projectPath in solutionProjects)
{
cancellationToken.ThrowIfCancellationRequested();
if (File.Exists(projectPath))
{
string projectDir = Path.GetDirectoryName(projectPath);
string projectName = Path.GetFileName(projectDir);
var projectItem = new FileItem
{
Name = projectName,
FullName = projectDir,
IsDirectory = true,
Parent = solutionItem,
IsSelected = false
};
await ProjectScannerService.BuildDirectoryTreeAsync(projectDir, projectItem, progress, cancellationToken);
if (projectItem.Children.Any())
{
solutionItem.Children.Add(projectItem);
}
}
processedProjects++;
progress?.Report((int)((processedProjects * 100.0) / totalProjects));
}
RootDirectory = solutionItem;
// После загрузки решения - не выбираем ничего по умолчанию
ClearAllSelections(solutionItem);
}
catch (Exception ex)
{
throw new Exception($"Ошибка при обработке решения: {ex.Message}", ex);
}
}
private List<string> ParseSolutionProjects(string solutionPath)
{
var projects = new List<string>();
try
{
string solutionDirectory = Path.GetDirectoryName(solutionPath);
foreach (string line in File.ReadAllLines(solutionPath))
{
if (line.Trim().StartsWith("Project(", StringComparison.OrdinalIgnoreCase))
{
var parts = line.Split(new[] { '"' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 3)
{
string relativePath = parts[2].Trim();
string absolutePath = Path.GetFullPath(Path.Combine(solutionDirectory, relativePath));
if (absolutePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase) && File.Exists(absolutePath))
{
projects.Add(absolutePath);
}
}
}
}
}
catch (Exception ex)
{
throw new Exception($"Ошибка парсинга файла решения: {ex.Message}", ex);
}
return projects;
}
private async Task LoadProjectDirectoryAsync(string projectDirectory, IProgress<int> progress, CancellationToken cancellationToken)
{
try
{
var projectName = Path.GetFileName(projectDirectory);
var rootItem = new FileItem
{
Name = projectName,
FullName = projectDirectory,
IsDirectory = true,
IsSelected = false
};
await ProjectScannerService.BuildDirectoryTreeAsync(projectDirectory, rootItem, progress, cancellationToken);
RootDirectory = rootItem;
// После загрузки проекта - не выбираем ничего по умолчанию
ClearAllSelections(rootItem);
}
catch (Exception ex)
{
throw new Exception($"Ошибка при загрузке проекта: {ex.Message}", ex);
}
}
private void ClearAllSelections(FileItem item)
{
item.IsSelected = false;
foreach (var child in item.Children)
{
ClearAllSelections(child);
}
}
private async void GenerateFileAsync(object parameter)
{
if (!CanGenerate || RootDirectory == null) return;
var selectedFiles = GetSelectedFiles(RootDirectory);
if (selectedFiles.Count == 0)
{
MessageBox.Show("Пожалуйста, выберите хотя бы один файл для обработки.", "Предупреждение", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
var saveDialog = new SaveFileDialog
{
Title = "Сохранить контекстный файл",
Filter = "Текстовые файлы (*.txt)|*.txt|Все файлы (*.*)|*.*",
FileName = $"{Path.GetFileNameWithoutExtension(SelectedProjectFilePath)}_context.txt",
DefaultExt = ".txt"
};
if (!string.IsNullOrEmpty(_lastProjectPath) && Directory.Exists(_lastProjectPath))
{
saveDialog.InitialDirectory = _lastProjectPath;
}
bool? result = saveDialog.ShowDialog();
if (result == true)
{
IsProcessing = true;
ProgressText = "Генерация контекстного файла...";
ProgressValue = 0;
try
{
_cancellationTokenSource = new CancellationTokenSource();
var progress = new Progress<int>(value =>
{
ProgressValue = value;
ProgressText = $"Генерация: {value}%";
});
await GenerateContextFileAsync(selectedFiles, saveDialog.FileName, progress, _cancellationTokenSource.Token);
MessageBox.Show($"Файл успешно создан:\n{saveDialog.FileName}", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (OperationCanceledException)
{
ProgressText = "Генерация отменена";
}
catch (IOException ex)
{
MessageBox.Show($"Ошибка доступа к файлу: {ex.Message}\nПожалуйста, закройте файлы или предоставьте необходимые права доступа.", "Ошибка доступа", MessageBoxButton.OK, MessageBoxImage.Error);
}
catch (Exception ex)
{
MessageBox.Show($"Ошибка при генерации файла: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
IsProcessing = false;
}
}
}
private List<string> GetSelectedFiles(FileItem rootItem)
{
var selectedFiles = new List<string>();
CollectSelectedFiles(rootItem, selectedFiles);
return selectedFiles;
}
private void CollectSelectedFiles(FileItem item, List<string> selectedFiles)
{
if (item.IsDirectory)
{
foreach (var child in item.Children)
{
CollectSelectedFiles(child, selectedFiles);
}
}
else if (item.IsSelected == true)
{
selectedFiles.Add(item.FullName);
}
}
private async Task GenerateContextFileAsync(List<string> selectedFiles, string outputPath, IProgress<int> progress, CancellationToken cancellationToken)
{
var outputContent = new StringBuilder();
int totalFiles = selectedFiles.Count;
int processedFiles = 0;
foreach (var filePath in selectedFiles)
foreach (var projectPath in solutionProjects)
{
cancellationToken.ThrowIfCancellationRequested();
try
if (File.Exists(projectPath))
{
string projectDir = Path.GetDirectoryName(SelectedProjectFilePath);
string relativePath = Path.GetRelativePath(projectDir, filePath);
string fileContent = await File.ReadAllTextAsync(filePath, Encoding.UTF8);
string? projectDir = Path.GetDirectoryName(projectPath);
string? projectName = Path.GetFileName(projectDir);
// Обработка комментариев
fileContent = FileProcessorService.ProcessFileContent(fileContent, Path.GetFileName(filePath));
fileContent = FileProcessorService.RemoveMultiLineComments(fileContent);
var projectItem = new FileItem
{
Name = projectName,
FullName = projectDir,
IsDirectory = true,
Parent = solutionItem,
IsSelected = false
};
outputContent.AppendLine($"=== Файл: {relativePath} ===");
outputContent.AppendLine(fileContent);
outputContent.AppendLine(); // Пустая строка между файлами
await ProjectScannerService.BuildDirectoryTreeAsync(projectDir, projectItem, progress, cancellationToken);
processedFiles++;
int progressValue = (int)((processedFiles * 100.0) / totalFiles);
progress?.Report(progressValue);
}
catch (IOException ex)
{
throw new IOException($"Файл '{Path.GetFileName(filePath)}' заблокирован или недоступен: {ex.Message}", ex);
if (projectItem.Children.Any())
{
solutionItem.Children.Add(projectItem);
}
}
processedProjects++;
progress?.Report((int)((processedProjects * 100.0) / totalProjects));
}
// Сохраняем результат с кодировкой UTF-8 с BOM
var encoding = new UTF8Encoding(true);
await File.WriteAllTextAsync(outputPath, outputContent.ToString(), encoding);
RootDirectory = solutionItem;
// После загрузки решения - не выбираем ничего по умолчанию
ClearAllSelections(solutionItem);
}
private void CancelProcessing(object parameter)
catch (Exception ex)
{
_cancellationTokenSource?.Cancel();
}
private void LoadSettings()
{
try
{
_lastProjectPath = Properties.Settings.Default.LastProjectPath;
}
catch
{
_lastProjectPath = null;
}
}
private void SaveSettings()
{
try
{
Properties.Settings.Default.LastProjectPath = _lastProjectPath;
Properties.Settings.Default.Save();
}
catch
{
// Игнорируем ошибки сохранения настроек
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
throw new Exception($"Ошибка при обработке решения: {ex.Message}", ex);
}
}
public class RelayCommand : ICommand
private static List<string> ParseSolutionProjects(string solutionPath)
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler? CanExecuteChanged
var projects = new List<string>();
try
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
string? solutionDirectory = Path.GetDirectoryName(solutionPath);
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
foreach (string line in File.ReadAllLines(solutionPath))
{
if (line.Trim().StartsWith("Project(", StringComparison.OrdinalIgnoreCase))
{
var parts = line.Split(['"'], StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 3)
{
string relativePath = parts[2].Trim();
string absolutePath = Path.GetFullPath(Path.Combine(solutionDirectory, relativePath));
if (absolutePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase) && File.Exists(absolutePath))
{
projects.Add(absolutePath);
}
}
}
}
}
catch (Exception ex)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
throw new Exception($"Ошибка парсинга файла решения: {ex.Message}", ex);
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
return projects;
}
private async Task LoadProjectDirectoryAsync(string projectDirectory, IProgress<int> progress, CancellationToken cancellationToken)
{
try
{
var projectName = Path.GetFileName(projectDirectory);
var rootItem = new FileItem
{
Name = projectName,
FullName = projectDirectory,
IsDirectory = true,
IsSelected = false
};
await ProjectScannerService.BuildDirectoryTreeAsync(projectDirectory, rootItem, progress, cancellationToken);
RootDirectory = rootItem;
// После загрузки проекта - не выбираем ничего по умолчанию
ClearAllSelections(rootItem);
}
catch (Exception ex)
{
throw new Exception($"Ошибка при загрузке проекта: {ex.Message}", ex);
}
}
private static void ClearAllSelections(FileItem item)
{
item.IsSelected = false;
foreach (var child in item.Children)
{
ClearAllSelections(child);
}
}
[RelayCommand(CanExecute = nameof(CanGenerate))]
private async Task GenerateFileAsync()
{
var selectedFiles = GetSelectedFiles(RootDirectory);
if (selectedFiles.Count == 0)
{
MessageBox.Show("Пожалуйста, выберите хотя бы один файл для обработки.", "Предупреждение", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
var saveDialog = new SaveFileDialog
{
Title = "Сохранить контекстный файл",
Filter = "Текстовые файлы (*.txt)|*.txt|Все файлы (*.*)|*.*",
FileName = $"{Path.GetFileNameWithoutExtension(SelectedProjectFilePath)}_context.txt",
DefaultExt = ".txt",
InitialDirectory = LastProjectPath
};
if (saveDialog.ShowDialog() != true) return;
IsProcessing = true;
try
{
_cancellationTokenSource = new CancellationTokenSource();
var progress = new Progress<int>(value =>
{
ProgressValue = value;
ProgressText = $"Генерация: {value}%";
});
await GenerateContextFileAsync(selectedFiles, saveDialog.FileName, progress, _cancellationTokenSource.Token);
MessageBox.Show($"Файл успешно создан:\n{saveDialog.FileName}", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (OperationCanceledException)
{
ProgressText = "Генерация отменена";
}
catch (IOException ex)
{
MessageBox.Show($"Ошибка доступа к файлу: {ex.Message}", "Ошибка доступа", MessageBoxButton.OK, MessageBoxImage.Error);
}
catch (Exception ex)
{
MessageBox.Show($"Ошибка при генерации файла: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
IsProcessing = false;
}
}
private List<string> GetSelectedFiles(FileItem rootItem)
{
var selectedFiles = new List<string>();
CollectSelectedFiles(rootItem, selectedFiles);
return selectedFiles;
}
private static void CollectSelectedFiles(FileItem item, List<string> selectedFiles)
{
if (item.IsDirectory)
{
foreach (var child in item.Children)
{
CollectSelectedFiles(child, selectedFiles);
}
}
else if (item.IsSelected == true)
{
selectedFiles.Add(item.FullName);
}
}
private async Task GenerateContextFileAsync(List<string> selectedFiles, string outputPath, IProgress<int> progress, CancellationToken cancellationToken)
{
var outputContent = new StringBuilder();
int totalFiles = selectedFiles.Count;
int processedFiles = 0;
foreach (var filePath in selectedFiles)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
string? projectDir = Path.GetDirectoryName(SelectedProjectFilePath);
string? relativePath = Path.GetRelativePath(projectDir, filePath);
string? fileContent = await File.ReadAllTextAsync(filePath, Encoding.UTF8);
// Обработка комментариев
fileContent = FileProcessorService.ProcessFileContent(fileContent, Path.GetFileName(filePath));
fileContent = FileProcessorService.RemoveMultiLineComments(fileContent);
outputContent.AppendLine($"=== Файл: {relativePath} ===");
outputContent.AppendLine(fileContent);
outputContent.AppendLine(); // Пустая строка между файлами
processedFiles++;
int progressValue = (int)((processedFiles * 100.0) / totalFiles);
progress?.Report(progressValue);
}
catch (IOException ex)
{
throw new IOException($"Файл '{Path.GetFileName(filePath)}' заблокирован или недоступен: {ex.Message}", ex);
}
}
// Сохраняем результат с кодировкой UTF-8 с BOM
var encoding = new UTF8Encoding(true);
await File.WriteAllTextAsync(outputPath, outputContent.ToString(), encoding);
}
[RelayCommand]
private void CancelProcessing()
{
_cancellationTokenSource?.Cancel();
}
private void LoadSettings()
{
try
{
LastProjectPath = Properties.Settings.Default.LastProjectPath;
}
catch
{
LastProjectPath = string.Empty;
}
}
private void SaveSettings()
{
try
{
Properties.Settings.Default.LastProjectPath = LastProjectPath;
Properties.Settings.Default.Save();
}
catch { }
}
/*public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}*/
}
/*
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler? CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}*/

View File

@@ -1,68 +1,95 @@
<Window x:Class="CodeContextGenerator.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CodeContextGenerator.Views"
mc:Ignorable="d"
Title="Code Context Generator" Height="600" Width="800"
WindowStartupLocation="CenterScreen">
<Window
x:Class="CodeContextGenerator.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CodeContextGenerator.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="clr-namespace:CodeContextGenerator.ViewModels"
Title="Code Context Generator"
Width="800"
Height="600"
d:DataContext="{d:DesignInstance Type=viewmodels:MainViewModel}"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Window.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="IsExpanded" Value="True" />
<Setter Property="Focusable" Value="False" />
</Style>
<Style TargetType="Button">
<Setter Property="Padding" Value="10,5"/>
<Setter Property="Margin" Value="5,0"/>
<Setter Property="Padding" Value="10,5" />
<Setter Property="Margin" Value="5,0" />
</Style>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Window.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Выберите файл решения (.sln) или проекта (.csproj):" FontWeight="Bold" Margin="0,0,0,5"/>
<TextBlock
Grid.Row="0"
Margin="0,0,0,5"
FontWeight="Bold"
Text="Выберите файл решения (.sln) или проекта (.csproj):" />
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,10">
<Button Content="Выбрать файл..."
Command="{Binding SelectProjectFileCommand}"
IsEnabled="{Binding CanSelectFolder}"
Width="120"/>
<TextBlock Text="{Binding SelectedProjectFilePath}"
VerticalAlignment="Center"
TextWrapping="Wrap"
MaxWidth="600"
Margin="10,0,0,0"/>
<StackPanel
Grid.Row="1"
Margin="0,0,0,10"
Orientation="Horizontal">
<Button
Width="120"
Command="{Binding SelectProjectFileCommand}"
Content="Выбрать файл..."
IsEnabled="{Binding CanSelectFolder}" />
<TextBlock
MaxWidth="600"
Margin="10,0,0,0"
VerticalAlignment="Center"
Text="{Binding SelectedProjectFilePath}"
TextWrapping="Wrap" />
</StackPanel>
<Border Grid.Row="2" BorderBrush="Gray" BorderThickness="1" CornerRadius="4" Padding="5"
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}}">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
<TreeView x:Name="ProjectTree" ItemsSource="{Binding RootDirectory.Children}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<Border
Grid.Row="2"
Padding="5"
BorderBrush="Gray"
BorderThickness="1"
CornerRadius="4"
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}}">
<ScrollViewer
HorizontalScrollBarVisibility="Auto"
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel"
VerticalScrollBarVisibility="Auto">
<TreeView
x:Name="ProjectTree"
ItemsSource="{Binding RootDirectory.Children}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" MinHeight="24">
<CheckBox IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}"
IsThreeState="True"
VerticalAlignment="Center" Margin="2,0,5,0"
Click="CheckBox_Click"/>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center"
ToolTip="{Binding FullName}"/>
<StackPanel MinHeight="24" Orientation="Horizontal">
<CheckBox
Margin="2,0,5,0"
VerticalAlignment="Center"
Click="CheckBox_Click"
IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}"
IsThreeState="True" />
<TextBlock
VerticalAlignment="Center"
Text="{Binding Name}"
ToolTip="{Binding FullName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
@@ -70,27 +97,49 @@
</ScrollViewer>
</Border>
<TextBlock Grid.Row="2" Text="Проект еще не загружен. Выберите файл решения или проекта."
HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="Gray" FontSize="14"
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Collapsed}"/>
<TextBlock
Grid.Row="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="14"
Foreground="Gray"
Text="Проект еще не загружен. Выберите файл решения или проекта."
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Collapsed}" />
<ProgressBar Grid.Row="3" Value="{Binding ProgressValue}" Height="20" Margin="0,10,0,10"
Visibility="{Binding IsProcessing, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<ProgressBar
Grid.Row="3"
Height="20"
Margin="0,10,0,10"
Visibility="{Binding IsProcessing, Converter={StaticResource BooleanToVisibilityConverter}}"
Value="{Binding ProgressValue}" />
<TextBlock Grid.Row="3" Text="{Binding ProgressText}" HorizontalAlignment="Center"
VerticalAlignment="Center" FontWeight="Bold"
Visibility="{Binding IsProcessing, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock
Grid.Row="3"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding ProgressText}"
Visibility="{Binding IsProcessing, Converter={StaticResource BooleanToVisibilityConverter}}" />
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
<Button Content="Отмена" Command="{Binding CancelCommand}"
Visibility="{Binding IsProcessing, Converter={StaticResource BooleanToVisibilityConverter}}"
Background="#FFDC3545" Foreground="White"/>
<Button Content="Закрыть" Command="{Binding ExitCommand}"/>
<Button Content="Сформировать" Command="{Binding GenerateCommand}"
IsEnabled="{Binding CanGenerate}"
Background="#FF28A745" Foreground="White"
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<StackPanel
Grid.Row="4"
Margin="0,10,0,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
Background="#FFDC3545"
Command="{Binding CancelCommand}"
Content="Отмена"
Foreground="White"
Visibility="{Binding IsProcessing, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button Command="{Binding ExitCommand}" Content="Закрыть" />
<Button
Background="#FF28A745"
Command="{Binding GenerateFileCommand}"
Content="Сформировать"
Foreground="White"
IsEnabled="{Binding CanGenerate}"
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}}" />
</StackPanel>
</Grid>
</Window>

View File

@@ -4,37 +4,31 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace CodeContextGenerator.Views
namespace CodeContextGenerator.Views;
public partial class MainWindow : Window
{
public partial class MainWindow : Window
public MainWindow()
{
public MainWindow()
{
InitializeComponent();
}
InitializeComponent();
DataContext = new MainViewModel();
}
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (sender is ScrollViewer scrollViewer)
{
if (sender is ScrollViewer scrollViewer)
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta / 3);
e.Handled = true;
}
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta / 3);
e.Handled = true;
}
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
if (sender is CheckBox checkBox && checkBox.DataContext is FileItem fileItem)
{
if (sender is CheckBox checkBox && checkBox.DataContext is FileItem fileItem)
{
// Принудительно обновляем состояние детей при клике
if (fileItem.IsDirectory && checkBox.IsChecked.HasValue)
{
foreach (var child in fileItem.Children)
{
child.IsSelected = checkBox.IsChecked;
}
}
}
// Синхронизация состояния чекбокса с ViewModel
fileItem.IsSelected = checkBox.IsChecked;
}
}
}