init FileBrowser
This commit is contained in:
88
.gitignore
vendored
Normal file
88
.gitignore
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# .NET Core build results
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# Windows image file caches
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
|
||||||
|
# Visual Studio for Mac
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Rider
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# Web/ASP.NET (not needed for WPF, but often included)
|
||||||
|
*.publish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
*.azurePubxml.user
|
||||||
|
PublishProfiles/
|
||||||
|
|
||||||
|
# NuGet
|
||||||
|
*.nupkg
|
||||||
|
*.snupkg
|
||||||
|
.nuget/
|
||||||
|
packages/
|
||||||
|
*.nuspec
|
||||||
|
project.assets.json
|
||||||
|
PackageRestoreEnabled.txt
|
||||||
|
|
||||||
|
# MSTest test results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# Visual Studio Code settings
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Backup & temp files
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Resharper
|
||||||
|
_ReSharper*/
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# Rider
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# WPF specific (if generated)
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Local config files
|
||||||
|
appsettings.Development.json
|
||||||
|
app.config
|
||||||
|
web.config
|
||||||
|
|
||||||
|
# Crash reports
|
||||||
|
*.dmp
|
||||||
|
|
||||||
|
# Others
|
||||||
|
*.dbmdl
|
||||||
|
*.jfm
|
||||||
|
*.sdf
|
||||||
|
*.cache
|
||||||
|
*.pdb
|
||||||
|
*.mdb
|
||||||
|
|
||||||
|
# Ignore git itself
|
||||||
|
.git/
|
||||||
|
|
||||||
|
|
||||||
|
.vs/
|
||||||
25
FileBrowser.sln
Normal file
25
FileBrowser.sln
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.11.35208.52
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileBrowser", "FileBrowser\FileBrowser.csproj", "{13CB098D-6CE7-4FC9-8E05-B5D9D3977B88}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{13CB098D-6CE7-4FC9-8E05-B5D9D3977B88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{13CB098D-6CE7-4FC9-8E05-B5D9D3977B88}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{13CB098D-6CE7-4FC9-8E05-B5D9D3977B88}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{13CB098D-6CE7-4FC9-8E05-B5D9D3977B88}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {567C0FE1-762F-4A8A-8F95-CEE68A3BDCF5}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
9
FileBrowser/App.xaml
Normal file
9
FileBrowser/App.xaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<Application x:Class="FileBrowser.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:FileBrowser"
|
||||||
|
Startup="OnStartup">
|
||||||
|
<Application.Resources>
|
||||||
|
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
30
FileBrowser/App.xaml.cs
Normal file
30
FileBrowser/App.xaml.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using FileBrowser.Services;
|
||||||
|
using FileBrowser.ViewModels;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace FileBrowser;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
private IServiceProvider _provider;
|
||||||
|
|
||||||
|
private void OnStartup(object sender, StartupEventArgs e)
|
||||||
|
{
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
|
||||||
|
services.AddSingleton<IFileService, FileService>();
|
||||||
|
services.AddSingleton<MainViewModel>();
|
||||||
|
services.AddTransient<MainWindow>(); // window created with DI
|
||||||
|
|
||||||
|
_provider = services.BuildServiceProvider();
|
||||||
|
|
||||||
|
var wnd = _provider.GetRequiredService<MainWindow>();
|
||||||
|
// Set DataContext with DI-provided VM (no code-behind in view)
|
||||||
|
wnd.DataContext = _provider.GetRequiredService<MainViewModel>();
|
||||||
|
wnd.Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
FileBrowser/AssemblyInfo.cs
Normal file
10
FileBrowser/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
||||||
15
FileBrowser/FileBrowser.csproj
Normal file
15
FileBrowser/FileBrowser.csproj
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
21
FileBrowser/Helpers/RelayCommand.cs
Normal file
21
FileBrowser/Helpers/RelayCommand.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace FileBrowser.Helpers;
|
||||||
|
|
||||||
|
public class RelayCommand : ICommand
|
||||||
|
{
|
||||||
|
private readonly Action<object?> _execute;
|
||||||
|
private readonly Func<object?, bool>? _canExecute;
|
||||||
|
|
||||||
|
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?.Invoke(parameter) ?? true;
|
||||||
|
public void Execute(object? parameter) => _execute(parameter);
|
||||||
|
|
||||||
|
public event EventHandler? CanExecuteChanged;
|
||||||
|
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
73
FileBrowser/MainWindow.xaml
Normal file
73
FileBrowser/MainWindow.xaml
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<Window x:Class="FileBrowser.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:FileBrowser"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="Keyboard File Browser" Height="600" Width="1000">
|
||||||
|
<Window.InputBindings>
|
||||||
|
<!-- Tab: Enter / Open -->
|
||||||
|
<KeyBinding Key="Tab" Command="{Binding EnterCommand}" />
|
||||||
|
<!-- Shift+Tab: Up one folder -->
|
||||||
|
<KeyBinding Key="Tab" Modifiers="Shift" Command="{Binding UpCommand}" />
|
||||||
|
<!-- F5 refresh -->
|
||||||
|
<KeyBinding Key="F5" Command="{Binding RefreshCommand}" />
|
||||||
|
</Window.InputBindings>
|
||||||
|
|
||||||
|
<DockPanel LastChildFill="True" Margin="6">
|
||||||
|
<!-- Top: Path -->
|
||||||
|
<TextBlock DockPanel.Dock="Top" Text="{Binding CurrentPath}" d:Text="c:/Test" FontWeight="Bold"
|
||||||
|
Padding="4" />
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="2*" />
|
||||||
|
<ColumnDefinition Width="4*" />
|
||||||
|
<ColumnDefinition Width="3*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Left: Parent folder contents -->
|
||||||
|
<Border Grid.Column="0" BorderBrush="Gray" BorderThickness="1" Margin="4"
|
||||||
|
Padding="4">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="Parent / Siblings" FontWeight="SemiBold" />
|
||||||
|
<ListBox ItemsSource="{Binding ParentItems}"
|
||||||
|
SelectedItem="{Binding SelectedParentItem}"
|
||||||
|
DisplayMemberPath="Name"
|
||||||
|
KeyboardNavigation.TabNavigation="Local"
|
||||||
|
Focusable="False" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Middle: Current folder contents and filter input (keyboard-only) -->
|
||||||
|
<Border Grid.Column="1" BorderBrush="Gray" BorderThickness="1" Margin="4"
|
||||||
|
Padding="4">
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock DockPanel.Dock="Top" Text="Current Folder (type to filter)" />
|
||||||
|
<!-- Hidden TextBox takes all typing for filter -->
|
||||||
|
<TextBox x:Name="FilterBox"
|
||||||
|
Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Height="0.0" Opacity="0" Focusable="True"/>
|
||||||
|
<ListBox ItemsSource="{Binding CurrentView}"
|
||||||
|
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
|
||||||
|
DisplayMemberPath="Name"
|
||||||
|
IsSynchronizedWithCurrentItem="True"
|
||||||
|
KeyboardNavigation.TabNavigation="Local" />
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Right: Preview -->
|
||||||
|
<Border Grid.Column="2" BorderBrush="Gray" BorderThickness="1" Margin="4"
|
||||||
|
Padding="4">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="Preview" FontWeight="SemiBold" />
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
|
||||||
|
<!-- Einfacher Text-Preview -->
|
||||||
|
<TextBlock Text="{Binding PreviewText}" TextWrapping="Wrap" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</DockPanel>
|
||||||
|
</Window>
|
||||||
14
FileBrowser/MainWindow.xaml.cs
Normal file
14
FileBrowser/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace FileBrowser;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
FileBrowser/Models/FileItem.cs
Normal file
10
FileBrowser/Models/FileItem.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FileBrowser.Models;
|
||||||
|
|
||||||
|
public class FileItem
|
||||||
|
{
|
||||||
|
public string FullPath { get; set; }
|
||||||
|
public string Name => Path.GetFileName(FullPath) ?? FullPath;
|
||||||
|
public bool IsDirectory => Directory.Exists(FullPath);
|
||||||
|
}
|
||||||
51
FileBrowser/Services/FileService.cs
Normal file
51
FileBrowser/Services/FileService.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using FileBrowser.Models;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FileBrowser.Services;
|
||||||
|
|
||||||
|
public class FileService : IFileService
|
||||||
|
{
|
||||||
|
public string GetParentDirectory(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var di = Directory.GetParent(path);
|
||||||
|
return di?.FullName ?? path;
|
||||||
|
}
|
||||||
|
catch { return path; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<FileItem> GetDirectoryItems(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dirs = Directory.EnumerateDirectories(path)
|
||||||
|
.Select(d => new FileItem { FullPath = d });
|
||||||
|
var files = Directory.EnumerateFiles(path)
|
||||||
|
.Select(f => new FileItem { FullPath = f });
|
||||||
|
return dirs.Concat(files).OrderBy(i => i.IsDirectory ? 0 : 1).ThenBy(i => i.Name);
|
||||||
|
}
|
||||||
|
catch { return Array.Empty<FileItem>(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? ReadTextPreview(string path, int maxChars = 20000)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ext = Path.GetExtension(path)?.ToLowerInvariant();
|
||||||
|
var textExts = new[] { ".txt", ".cs", ".config", ".json", ".xml", ".log", ".md", ".csv" };
|
||||||
|
if (!textExts.Contains(ext)) return null;
|
||||||
|
using var sr = new StreamReader(path);
|
||||||
|
var s = sr.ReadToEnd();
|
||||||
|
if (s.Length > maxChars) s = s.Substring(0, maxChars) + "\n... (truncated)";
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
catch { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsImage(string path)
|
||||||
|
{
|
||||||
|
var ext = Path.GetExtension(path)?.ToLowerInvariant();
|
||||||
|
return ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp" || ext == ".gif";
|
||||||
|
}
|
||||||
|
}
|
||||||
11
FileBrowser/Services/IFileService.cs
Normal file
11
FileBrowser/Services/IFileService.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using FileBrowser.Models;
|
||||||
|
|
||||||
|
namespace FileBrowser.Services;
|
||||||
|
|
||||||
|
public interface IFileService
|
||||||
|
{
|
||||||
|
string GetParentDirectory(string path);
|
||||||
|
IEnumerable<FileItem> GetDirectoryItems(string path);
|
||||||
|
string? ReadTextPreview(string path, int maxChars = 20000);
|
||||||
|
bool IsImage(string path);
|
||||||
|
}
|
||||||
242
FileBrowser/ViewModels/MainViewModel.cs
Normal file
242
FileBrowser/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
using FileBrowser.Helpers;
|
||||||
|
using FileBrowser.Models;
|
||||||
|
using FileBrowser.Services;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace FileBrowser.ViewModels;
|
||||||
|
|
||||||
|
public class MainViewModel : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private readonly IFileService _fileService;
|
||||||
|
|
||||||
|
public MainViewModel(IFileService fileService)
|
||||||
|
{
|
||||||
|
_fileService = fileService ?? throw new ArgumentNullException(nameof(fileService));
|
||||||
|
|
||||||
|
ParentItems = new ObservableCollection<FileItem>();
|
||||||
|
CurrentItems = new ObservableCollection<FileItem>();
|
||||||
|
|
||||||
|
// Commands zuerst initialisieren
|
||||||
|
EnterCommand = new RelayCommand(_ => EnterOrOpen(), _ => SelectedItem != null);
|
||||||
|
UpCommand = new RelayCommand(_ => Up(), _ => CanGoUp());
|
||||||
|
RefreshCommand = new RelayCommand(_ => Refresh());
|
||||||
|
|
||||||
|
// CollectionView danach erzeugen
|
||||||
|
_currentView = CollectionViewSource.GetDefaultView(CurrentItems);
|
||||||
|
if (_currentView != null)
|
||||||
|
_currentView.Filter = FilterPredicate;
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
CurrentPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) ?? @"C:\";
|
||||||
|
FilterText = string.Empty;
|
||||||
|
|
||||||
|
// Jetzt sicher Refresh aufrufen (SelectedItem kann gesetzt werden ohne Nullref)
|
||||||
|
try { Refresh(); }
|
||||||
|
catch (Exception ex) { Debug.WriteLine("Initial Refresh failed: " + ex); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
private void Raise(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||||
|
|
||||||
|
private string _currentPath = "";
|
||||||
|
public string CurrentPath
|
||||||
|
{
|
||||||
|
get => _currentPath;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_currentPath == value) return;
|
||||||
|
_currentPath = value;
|
||||||
|
Raise(nameof(CurrentPath));
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<FileItem> ParentItems { get; } = new ObservableCollection<FileItem>();
|
||||||
|
public ObservableCollection<FileItem> CurrentItems { get; } = new ObservableCollection<FileItem>();
|
||||||
|
|
||||||
|
private ICollectionView _currentView;
|
||||||
|
public ICollectionView CurrentView => _currentView;
|
||||||
|
|
||||||
|
private FileItem? _selectedParentItem;
|
||||||
|
public FileItem? SelectedParentItem
|
||||||
|
{
|
||||||
|
get => _selectedParentItem;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_selectedParentItem = value;
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
// selecting a sibling sets CurrentPath to its parent (no auto-enter)
|
||||||
|
}
|
||||||
|
Raise(nameof(SelectedParentItem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileItem? _selectedItem;
|
||||||
|
public FileItem? SelectedItem
|
||||||
|
{
|
||||||
|
get => _selectedItem;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_selectedItem = value;
|
||||||
|
UpdatePreview();
|
||||||
|
Raise(nameof(SelectedItem));
|
||||||
|
// Sicherstellen, dass EnterCommand existiert und vom erwarteten Typ ist,
|
||||||
|
// bevor RaiseCanExecuteChanged aufgerufen wird.
|
||||||
|
if (EnterCommand is RelayCommand rc)
|
||||||
|
{
|
||||||
|
rc.RaiseCanExecuteChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? _previewText;
|
||||||
|
public string? PreviewText
|
||||||
|
{
|
||||||
|
get => _previewText;
|
||||||
|
set { _previewText = value; Raise(nameof(PreviewText)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _filterText = "";
|
||||||
|
public string FilterText
|
||||||
|
{
|
||||||
|
get => _filterText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_filterText = value;
|
||||||
|
_currentView.Refresh();
|
||||||
|
Raise(nameof(FilterText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool FilterPredicate(object obj)
|
||||||
|
{
|
||||||
|
if (obj is FileItem fi)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(FilterText)) return true;
|
||||||
|
return fi.Name.IndexOf(FilterText, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePreview()
|
||||||
|
{
|
||||||
|
PreviewText = null;
|
||||||
|
if (SelectedItem == null) return;
|
||||||
|
|
||||||
|
// Schütze _fileService gegen Null
|
||||||
|
if (_fileService == null) return;
|
||||||
|
|
||||||
|
if (SelectedItem.IsDirectory) return;
|
||||||
|
|
||||||
|
var txt = _fileService.ReadTextPreview(SelectedItem.FullPath);
|
||||||
|
if (txt != null)
|
||||||
|
{
|
||||||
|
PreviewText = txt;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_fileService.IsImage(SelectedItem.FullPath))
|
||||||
|
{
|
||||||
|
PreviewText = SelectedItem.FullPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand EnterCommand { get; }
|
||||||
|
public ICommand UpCommand { get; }
|
||||||
|
public ICommand RefreshCommand { get; }
|
||||||
|
|
||||||
|
private void Refresh()
|
||||||
|
{
|
||||||
|
// Defensive programming to avoid NullReferenceExceptions
|
||||||
|
if (ParentItems == null || CurrentItems == null)
|
||||||
|
throw new InvalidOperationException("Collections were not initialized.");
|
||||||
|
|
||||||
|
// clear existing
|
||||||
|
ParentItems.Clear();
|
||||||
|
CurrentItems.Clear();
|
||||||
|
|
||||||
|
// normalize CurrentPath
|
||||||
|
var path = string.IsNullOrWhiteSpace(CurrentPath) ? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) : CurrentPath;
|
||||||
|
|
||||||
|
// Determine parent folder for sibling listing; if none, use the same path
|
||||||
|
string parentForSiblings;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var parentDirInfo = Directory.GetParent(path);
|
||||||
|
parentForSiblings = parentDirInfo?.FullName ?? path;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("Directory.GetParent failed: " + ex);
|
||||||
|
parentForSiblings = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get items safely from service (service should itself be robust)
|
||||||
|
IEnumerable<FileItem> parentItems;
|
||||||
|
try { parentItems = _fileService.GetDirectoryItems(parentForSiblings) ?? Array.Empty<FileItem>(); }
|
||||||
|
catch (Exception ex) { Debug.WriteLine("GetDirectoryItems(parent) failed: " + ex); parentItems = Array.Empty<FileItem>(); }
|
||||||
|
|
||||||
|
foreach (var item in parentItems) ParentItems.Add(item);
|
||||||
|
|
||||||
|
IEnumerable<FileItem> currentItems;
|
||||||
|
try { currentItems = _fileService.GetDirectoryItems(path) ?? Array.Empty<FileItem>(); }
|
||||||
|
catch (Exception ex) { Debug.WriteLine("GetDirectoryItems(current) failed: " + ex); currentItems = Array.Empty<FileItem>(); }
|
||||||
|
|
||||||
|
foreach (var item in currentItems) CurrentItems.Add(item);
|
||||||
|
|
||||||
|
// Refresh view safely
|
||||||
|
try { _currentView?.Refresh(); }
|
||||||
|
catch (Exception ex) { Debug.WriteLine("CollectionView.Refresh failed: " + ex); }
|
||||||
|
|
||||||
|
// Select first available safely
|
||||||
|
SelectedItem = CurrentItems.FirstOrDefault();
|
||||||
|
SelectedParentItem = ParentItems.FirstOrDefault(o => o.FullPath.Contains(CurrentPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)));
|
||||||
|
|
||||||
|
// Notify (collections changed notifications come from ObservableCollection,
|
||||||
|
// but we raise to be safe)
|
||||||
|
Raise(nameof(ParentItems));
|
||||||
|
Raise(nameof(CurrentItems));
|
||||||
|
Raise(nameof(CurrentView));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanGoUp() => Directory.GetParent(CurrentPath) != null;
|
||||||
|
|
||||||
|
private void Up()
|
||||||
|
{
|
||||||
|
var parent = Directory.GetParent(CurrentPath)?.FullName;
|
||||||
|
if (parent != null)
|
||||||
|
{
|
||||||
|
CurrentPath = parent;
|
||||||
|
FilterText = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnterOrOpen()
|
||||||
|
{
|
||||||
|
if (SelectedItem == null) return;
|
||||||
|
if (SelectedItem.IsDirectory)
|
||||||
|
{
|
||||||
|
CurrentPath = SelectedItem.FullPath;
|
||||||
|
FilterText = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ProcessStartInfo psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = SelectedItem.FullPath,
|
||||||
|
UseShellExecute = true
|
||||||
|
};
|
||||||
|
Process.Start(psi);
|
||||||
|
}
|
||||||
|
catch { /* ignore failures */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user