diff --git a/ExplorerEx/Assets/Geometries.xaml b/ExplorerEx/Assets/Geometries.xaml index b0a790f..23dbf23 100644 --- a/ExplorerEx/Assets/Geometries.xaml +++ b/ExplorerEx/Assets/Geometries.xaml @@ -235,10 +235,10 @@ - + - + @@ -1005,6 +1005,129 @@ + M532.5 513h-206V495h206A19.6 19.6 0 00552 475.5v-412A19.6 19.6 0 00532.5 44h-412A19.6 19.6 0 00101 63.5v412A19.6 19.6 0 00120.5 495h8.7v18h-8.7A37.6 37.6 0 0183 475.5v-412A37.6 37.6 0 01120.5 26h412A37.6 37.6 0 01570 63.5v412A37.6 37.6 0 01532.5 513ZM426.3 462.2l-64.8-41C209.4 573.4 40 582 40 582c153-34.8 224.4-222.2 224.4-222.2l-64.8-41 216-90.5 10.6 234Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1013,6 +1136,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + M 955.1 433.2 H 301.3 c -17.7 0 -25.6 -21.7 -13.8 -33.5 l 189 -189 c 11.8 -11.8 11.8 -29.5 0 -41.4 L 433.2 126 c -11.8 -11.8 -29.5 -11.8 -41.4 0 L 47.3 470.6 c -11.8 11.8 -11.8 29.5 0 41.4 l 344.6 344.6 c 11.8 11.8 29.5 11.8 41.4 0 l 41.4 -41.4 c 11.8 -11.8 11.8 -29.5 0 -41.4 l -189 -189 c -11.8 -11.8 -3.9 -33.5 13.8 -33.5 h 653.8 c 15.8 0 29.5 -13.8 29.5 -29.5 v -59.1 c 2 -15.8 -11.8 -29.5 -27.6 -29.5 z M -955.1 433.2 H -301.3 c 17.7 0 25.6 -21.7 13.8 -33.5 l -189 -189 c -11.8 -11.8 -11.8 -29.5 0 -41.4 L -433.2 126 c 11.8 -11.8 29.5 -11.8 41.4 0 L -47.3 470.6 c 11.8 11.8 11.8 29.5 0 41.4 l -344.6 344.6 c -11.8 11.8 -29.5 11.8 -41.4 0 l -41.4 -41.4 c -11.8 -11.8 -11.8 -29.5 0 -41.4 l 189 -189 c 11.8 -11.8 3.9 -33.5 -13.8 -33.5 h -653.8 c -15.8 0 -29.5 -13.8 -29.5 -29.5 v -59.1 c -2 -15.8 11.8 -29.5 27.6 -29.5 z M 414 809.7 V 155.9 c 0 -17.7 -21.7 -25.6 -33.5 -13.8 l -189 189 c -11.8 11.8 -29.5 11.8 -41.4 0 l -43.3 -43.3 c -11.8 -11.8 -11.8 -29.5 0 -41.4 l 344.6 -344.5 c 11.8 -11.8 29.5 -11.8 41.4 0 l 344.6 344.6 c 11.8 11.8 11.8 29.5 0 41.4 L 796 329.3 c -11.8 11.8 -29.5 11.8 -41.4 0 l -189 -189 c -11.8 -11.8 -33.5 -3.9 -33.5 13.8 v 653.8 c 0 15.8 -13.8 29.5 -29.5 29.5 h -59.1 c -15.8 2 -29.5 -11.8 -29.5 -27.6 Z diff --git a/ExplorerEx/Assets/Settings.xml b/ExplorerEx/Assets/Settings.xml index c88a1c6..bae3c97 100644 --- a/ExplorerEx/Assets/Settings.xml +++ b/ExplorerEx/Assets/Settings.xml @@ -1,33 +1,42 @@  - - + + + + - - + + - - + + - + + - - + + \ No newline at end of file diff --git a/ExplorerEx/Assets/Svg.zip b/ExplorerEx/Assets/Svg.zip deleted file mode 100644 index 7f645d6..0000000 Binary files a/ExplorerEx/Assets/Svg.zip and /dev/null differ diff --git a/ExplorerEx/Assets/Svg/Background.svg b/ExplorerEx/Assets/Svg/Background.svg new file mode 100644 index 0000000..c9eaf3a --- /dev/null +++ b/ExplorerEx/Assets/Svg/Background.svg @@ -0,0 +1,3 @@ + + + diff --git a/ExplorerEx/Assets/Svg/Blur.svg b/ExplorerEx/Assets/Svg/Blur.svg new file mode 100644 index 0000000..619cdc2 --- /dev/null +++ b/ExplorerEx/Assets/Svg/Blur.svg @@ -0,0 +1,3 @@ + + + diff --git a/ExplorerEx/Assets/Svg/Click.svg b/ExplorerEx/Assets/Svg/Click.svg new file mode 100644 index 0000000..aa0e8c4 --- /dev/null +++ b/ExplorerEx/Assets/Svg/Click.svg @@ -0,0 +1,3 @@ + + + diff --git a/ExplorerEx/Assets/Svg/Color.svg b/ExplorerEx/Assets/Svg/Color.svg new file mode 100644 index 0000000..323de5c --- /dev/null +++ b/ExplorerEx/Assets/Svg/Color.svg @@ -0,0 +1,3 @@ + + + diff --git a/ExplorerEx/Assets/Svg/Eye.svg b/ExplorerEx/Assets/Svg/Eye.svg new file mode 100644 index 0000000..af3bf7a --- /dev/null +++ b/ExplorerEx/Assets/Svg/Eye.svg @@ -0,0 +1,3 @@ + + + diff --git a/ExplorerEx/Assets/Svg/Image.svg b/ExplorerEx/Assets/Svg/Image.svg new file mode 100644 index 0000000..0ce5b6d --- /dev/null +++ b/ExplorerEx/Assets/Svg/Image.svg @@ -0,0 +1,3 @@ + + + diff --git a/ExplorerEx/Assets/Svg/Language.svg b/ExplorerEx/Assets/Svg/Language.svg new file mode 100644 index 0000000..866d325 --- /dev/null +++ b/ExplorerEx/Assets/Svg/Language.svg @@ -0,0 +1,3 @@ + + + diff --git a/ExplorerEx/Assets/Svg/Share.svg b/ExplorerEx/Assets/Svg/Share.svg index fac98ef..737dfe1 100644 --- a/ExplorerEx/Assets/Svg/Share.svg +++ b/ExplorerEx/Assets/Svg/Share.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/ExplorerEx/Assets/Svg/TextEditor.svg b/ExplorerEx/Assets/Svg/TextEditor.svg new file mode 100644 index 0000000..9822a01 --- /dev/null +++ b/ExplorerEx/Assets/Svg/TextEditor.svg @@ -0,0 +1,3 @@ + + + diff --git a/ExplorerEx/Command/FileItemCommand.cs b/ExplorerEx/Command/FileItemCommand.cs index 9297b18..ac41c68 100644 --- a/ExplorerEx/Command/FileItemCommand.cs +++ b/ExplorerEx/Command/FileItemCommand.cs @@ -130,7 +130,7 @@ public async void Execute(object? param) { } var items = Items; switch (items.Count) { - case <= 0: + case 0: return; case 1: FileTabControl.MouseOverTabControl?.SelectedTab.StartRename(items[0]); @@ -141,16 +141,20 @@ public async void Execute(object? param) { } break; } - case "Delete": // 删除一个或多个文件,按住shift就是强制删除 + case "Delete": { // 删除一个或多个文件,按住shift就是强制删除 if (Folder.IsReadonly) { break; } - if ((Keyboard.Modifiers & ModifierKeys.Shift) != ModifierKeys.Shift) { // 没有按Shift + var items = Items; + if (items.Count == 0) { + break; + } + if ((Keyboard.Modifiers & ModifierKeys.Shift) != ModifierKeys.Shift) { // 没有按Shift if (!ContentDialog.ShowWithDefault(Settings.CommonSettings.DontAskWhenRecycle, "#AreYouSureToRecycleTheseFiles".L())) { return; } try { - await FileUtils.FileOperation(FileOpType.Delete, Items.Where(item => item is FileSystemItem).Select(item => item.FullPath).ToArray()); + await FileUtils.FileOperation(FileOpType.Delete, items.Where(item => item is FileSystemItem).Select(item => item.FullPath).ToArray()); } catch (Exception e) { Logger.Exception(e); } @@ -159,7 +163,7 @@ public async void Execute(object? param) { return; } var failedFiles = new List(); - foreach (var item in Items) { + foreach (var item in items) { if (item is FileSystemItem fsi) { try { if (fsi.IsFolder) { @@ -178,6 +182,7 @@ public async void Execute(object? param) { } } break; + } case "AddToBookmarks": TabControlProvider.Invoke()?.MainWindow.AddToBookmarks(Items.Select(i => i.FullPath).ToArray()); break; diff --git a/ExplorerEx/Converter/DictionaryConverter.cs b/ExplorerEx/Converter/DictionaryConverter.cs index 37d04ff..1f1e8c2 100644 --- a/ExplorerEx/Converter/DictionaryConverter.cs +++ b/ExplorerEx/Converter/DictionaryConverter.cs @@ -8,10 +8,10 @@ namespace ExplorerEx.Converter; public class DependencyKeyValuePair : DependencyObject { - public object Key { get; set; } + public object Key { get; set; } = null!; public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( - "Value", typeof(object), typeof(DependencyKeyValuePair), new PropertyMetadata(default(object))); + nameof(Value), typeof(object), typeof(DependencyKeyValuePair), new PropertyMetadata(default(object))); public object Value { get => GetValue(ValueProperty); @@ -42,7 +42,7 @@ public DictionaryConverter() { }; } - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { + public object? Convert(object? value, Type targetType, object parameter, CultureInfo culture) { if (value == null) { return null; } @@ -53,6 +53,6 @@ public object Convert(object value, Type targetType, object parameter, CultureIn } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - throw new NotImplementedException(); + throw new InvalidOperationException(); } } \ No newline at end of file diff --git a/ExplorerEx/Converter/FileAssocTemplateSelector.cs b/ExplorerEx/Converter/FileAssocTemplateSelector.cs index d78c568..51d99dd 100644 --- a/ExplorerEx/Converter/FileAssocTemplateSelector.cs +++ b/ExplorerEx/Converter/FileAssocTemplateSelector.cs @@ -2,14 +2,16 @@ using System.Windows.Controls; using ExplorerEx.Model; -namespace ExplorerEx.Converter; +namespace ExplorerEx.Converter; -internal class FileAssocTemplateSelector : StyleSelector { - public Style DefaultStyle { get; set; } +internal class FileAssocTemplateSelector : StyleSelector +{ + public Style DefaultStyle { get; set; } = null!; - public Style CustomStyle { get; set; } + public Style CustomStyle { get; set; } = null!; - public override Style SelectStyle(object item, DependencyObject container) { - return item is FileAssocItem ? CustomStyle : DefaultStyle; - } + public override Style SelectStyle(object item, DependencyObject container) + { + return item is FileAssocItem ? CustomStyle : DefaultStyle; + } } \ No newline at end of file diff --git a/ExplorerEx/Converter/FileViewItemDetailsConverter.cs b/ExplorerEx/Converter/FileViewItemDetailsConverter.cs index b3eaee2..a46d40a 100644 --- a/ExplorerEx/Converter/FileViewItemDetailsConverter.cs +++ b/ExplorerEx/Converter/FileViewItemDetailsConverter.cs @@ -22,8 +22,11 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu internal class ItemsCount2TextConverter : IValueConverter { public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) { - if (value is int i and > 0) { - return string.Format("...Items".L(), i); + if (value is int i) { + if (i > 1) { + return string.Format("...Items".L(), i); + } + return string.Format("...Item".L(), i); } return null; } diff --git a/ExplorerEx/Converter/SettingsPanelItemTemplateSelector.cs b/ExplorerEx/Converter/SettingsPanelItemTemplateSelector.cs new file mode 100644 index 0000000..7e7f5eb --- /dev/null +++ b/ExplorerEx/Converter/SettingsPanelItemTemplateSelector.cs @@ -0,0 +1,26 @@ +using System.Windows; +using System.Windows.Controls; + +namespace ExplorerEx.Converter; + +internal class SettingsPanelItemTemplateSelector : DataTemplateSelector { + public DataTemplate SettingsSelectItemTemplate { get; set; } = null!; + + public DataTemplate SettingsStringItemTemplate { get; set; } = null!; + + public DataTemplate SettingsBooleanItemTemplate { get; set; } = null!; + + public DataTemplate SettingsNumberItemTemplate { get; set; } = null!; + + public DataTemplate SettingsExpanderTemplate { get; set; } = null!; + + public override DataTemplate SelectTemplate(object item, DependencyObject container) { + return item switch { + SettingsSelectItem => SettingsSelectItemTemplate, + SettingsStringItem => SettingsStringItemTemplate, + SettingsBooleanItem => SettingsBooleanItemTemplate, + SettingsNumberItem => SettingsNumberItemTemplate, + _ => SettingsExpanderTemplate + }; + } +} \ No newline at end of file diff --git a/ExplorerEx/Database/EntityFramework/BookmarkEfContext.cs b/ExplorerEx/Database/EntityFramework/BookmarkEfContext.cs index 525e383..4815a0c 100644 --- a/ExplorerEx/Database/EntityFramework/BookmarkEfContext.cs +++ b/ExplorerEx/Database/EntityFramework/BookmarkEfContext.cs @@ -1,8 +1,8 @@ using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using System.Windows; @@ -31,7 +31,18 @@ public BookmarkEfContext() { dbPath = Path.Combine(path, "BookMarks.db"); } - public async Task LoadDataBase() { + protected override void OnConfiguring(DbContextOptionsBuilder ob) { + ob.UseSqlite($"Data Source={dbPath}"); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) { + modelBuilder.Entity().HasOne(b => b.Category) + .WithMany(cb => cb.Children).HasForeignKey(b => b.CategoryForeignKey); + } + + #region Interfaces + + public async Task LoadAsync() { try { await Database.EnsureCreatedAsync(); await BookmarkCategoryDbSet.LoadAsync(); @@ -60,69 +71,29 @@ await Task.Run(() => { } } - protected override void OnConfiguring(DbContextOptionsBuilder ob) { - ob.UseSqlite($"Data Source={dbPath}"); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().HasOne(b => b.Category) - .WithMany(cb => cb.Children).HasForeignKey(b => b.CategoryForeignKey); - } + public void Save() => base.SaveChanges(); - #region Interfaces + public Task SaveAsync() => base.SaveChangesAsync(); public void Add(BookmarkItem bookmark) { BookmarkDbSet.Add(bookmark); } - public Task AddAsync(BookmarkItem bookmark) { - return BookmarkDbSet.AddAsync(bookmark).AsTask(); - } public void Add(BookmarkCategory category) { BookmarkCategoryDbSet.Add(category); } - public Task AddAsync(BookmarkCategory category) { - return BookmarkCategoryDbSet.AddAsync(category).AsTask(); - } - - public Task LoadAsync() { - throw new NotImplementedException(); - } - public void Save() { - base.SaveChanges(); - } + public void Remove(BookmarkItem bookmark) => base.Remove(bookmark); - public Task SaveAsync() { - return base.SaveChangesAsync(); - } + public bool Contains(BookmarkItem bookmark) => BookmarkDbSet.Contains(bookmark); - public void Remove(BookmarkItem bookmark) { - base.Remove(bookmark); - } - - public bool Contains(BookmarkItem bookmark) { - return BookmarkDbSet.Contains(bookmark); - } + public bool Any(Expression> match) => BookmarkDbSet.Any(match); - public bool Any(Func match) { - return BookmarkDbSet.Any(match); - } - - public ObservableCollection GetBindable() { - return BookmarkCategoryDbSet.Local.ToObservableCollection(); - } - - #region ProbablyModify - public BookmarkCategory? FirstOrDefault(Func match) { - return BookmarkCategoryDbSet.FirstOrDefault(match); - } - - public BookmarkItem? FirstOrDefault(Func match) { - return BookmarkDbSet.FirstOrDefault(match); - } - - #endregion + public ObservableCollection GetBindable() => BookmarkCategoryDbSet.Local.ToObservableCollection(); + + public BookmarkCategory? FirstOrDefault(Expression> match) => BookmarkCategoryDbSet.FirstOrDefault(match); + public BookmarkItem? FirstOrDefault(Expression> match) => BookmarkDbSet.FirstOrDefault(match); + #endregion } \ No newline at end of file diff --git a/ExplorerEx/Database/EntityFramework/FileViewEfContext.cs b/ExplorerEx/Database/EntityFramework/FileViewEfContext.cs index cd85b56..12d3130 100644 --- a/ExplorerEx/Database/EntityFramework/FileViewEfContext.cs +++ b/ExplorerEx/Database/EntityFramework/FileViewEfContext.cs @@ -1,7 +1,7 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using System.Windows; @@ -29,18 +29,6 @@ protected override void OnConfiguring(DbContextOptionsBuilder ob) { ob.UseSqlite($"Data Source={dbPath}"); } - public bool Any(Func match) { - return FileViewDbSet.Any(match); - } - - public void Add(FileView item) { - FileViewDbSet.Add(item); - } - - public Task AddAsync(FileView item) { - return FileViewDbSet.AddAsync(item).AsTask(); - } - public async Task LoadAsync() { try { await Database.EnsureCreatedAsync(); @@ -51,19 +39,19 @@ public async Task LoadAsync() { } } - public void Save() { - base.SaveChanges(); - } + public void Save() => base.SaveChanges(); - public Task SaveAsync() { - return base.SaveChangesAsync(); - } + public Task SaveAsync() => base.SaveChangesAsync(); - public FileView? FirstOrDefault(Func match) { - return FileViewDbSet.FirstOrDefault(match); - } + public bool Any(Expression> match) => FileViewDbSet.Any(match); - public bool Contains(FileView fileView) { - return FileViewDbSet.Contains(fileView); + public void Add(FileView item) => FileViewDbSet.Add(item); + + public void Update(FileView item) { + // No need to update } + + public FileView? FirstOrDefault(Expression> match) => FileViewDbSet.FirstOrDefault(match); + + public bool Contains(FileView fileView) => FileViewDbSet.Contains(fileView); } \ No newline at end of file diff --git a/ExplorerEx/Database/Interface/IBookmarkDbContext.cs b/ExplorerEx/Database/Interface/IBookmarkDbContext.cs index 3080f8d..741c42b 100644 --- a/ExplorerEx/Database/Interface/IBookmarkDbContext.cs +++ b/ExplorerEx/Database/Interface/IBookmarkDbContext.cs @@ -1,23 +1,20 @@ using System; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Threading.Tasks; +using System.Linq.Expressions; using ExplorerEx.Model; namespace ExplorerEx.Database.Interface; public interface IBookmarkDbContext : IDatabase { void Add(BookmarkItem bookmark); - Task AddAsync(BookmarkItem bookmark); - public BookmarkItem? FirstOrDefault(Func match); + public BookmarkItem? FirstOrDefault(Expression> match); void Remove(BookmarkItem bookmark); bool Contains(BookmarkItem bookmark); - bool Any(Func match); + bool Any(Expression> match); - BookmarkCategory? FirstOrDefault(Func match); + BookmarkCategory? FirstOrDefault(Expression> match); void Add(BookmarkCategory category); - Task AddAsync(BookmarkCategory category); ObservableCollection GetBindable(); } \ No newline at end of file diff --git a/ExplorerEx/Database/Interface/IDatabase.cs b/ExplorerEx/Database/Interface/IDatabase.cs index 52a157a..60e45ef 100644 --- a/ExplorerEx/Database/Interface/IDatabase.cs +++ b/ExplorerEx/Database/Interface/IDatabase.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using ExplorerEx.Model; +using System.Threading.Tasks; namespace ExplorerEx.Database.Interface; diff --git a/ExplorerEx/Database/Interface/IDbContext.cs b/ExplorerEx/Database/Interface/IDbContext.cs new file mode 100644 index 0000000..4da77eb --- /dev/null +++ b/ExplorerEx/Database/Interface/IDbContext.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq.Expressions; + +namespace ExplorerEx.Database.Interface; + +public interface IDbContext { + void Add(T item); + public T? FirstOrDefault(Expression> match); + void Remove(T item); + bool Contains(T item); + bool Any(Expression> match); + + int Count(); +} \ No newline at end of file diff --git a/ExplorerEx/Database/Interface/IFileViewDbContext.cs b/ExplorerEx/Database/Interface/IFileViewDbContext.cs index fcc0959..c5dc820 100644 --- a/ExplorerEx/Database/Interface/IFileViewDbContext.cs +++ b/ExplorerEx/Database/Interface/IFileViewDbContext.cs @@ -1,14 +1,17 @@ using System; -using System.Threading.Tasks; +using System.Linq.Expressions; using ExplorerEx.Model; namespace ExplorerEx.Database.Interface; public interface IFileViewDbContext : IDatabase { - FileView? FirstOrDefault(Func match); + FileView? FirstOrDefault(Expression> match); bool Contains(FileView fileView); - bool Any(Func match); - + bool Any(Expression> match); void Add(FileView item); - Task AddAsync(FileView item); + /// + /// 由于不带Cache跟踪,所以需要手动更新数据 + /// + /// + void Update(FileView item); } \ No newline at end of file diff --git a/ExplorerEx/Database/SqlSugar/BookmarkSugarContext.cs b/ExplorerEx/Database/SqlSugar/BookmarkSugarContext.cs index 9258d34..13c1180 100644 --- a/ExplorerEx/Database/SqlSugar/BookmarkSugarContext.cs +++ b/ExplorerEx/Database/SqlSugar/BookmarkSugarContext.cs @@ -1,7 +1,6 @@ using System; -using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using System.Windows; using ExplorerEx.Database.Interface; @@ -10,37 +9,34 @@ namespace ExplorerEx.Database.SqlSugar; -public class BookmarkSugarContext : SugarContext, IBookmarkDbContext { - private readonly SugarCache itemSugarCache; - private readonly SugarCache categorySugarCache; - - - public BookmarkSugarContext() : base("BookMarks.db") { - itemSugarCache = new SugarCache(ConnectionClient); - categorySugarCache = new SugarCache(ConnectionClient, - new SugarStrategy(itemSugarCache, (category, item) => { - if (item.CategoryForeignKey == category.Name) { - item.Category = category; - category.Children?.Add(item); - } - })); +public class BookmarkSugarContext : IBookmarkDbContext { + private readonly CachedSugarContext bookmarkCtx; + private readonly CachedSugarContext categoryCtx; + + public BookmarkSugarContext() { + categoryCtx = new CachedSugarContext("BookMarks.db"); + bookmarkCtx = new CachedSugarContext("BookMarks.db"); + //categorySugarCache = new SugarCache(ConnectionClient, + // new SugarStrategy(itemSugarCache, (category, item) => { + // if (item.CategoryForeignKey == category.Name) { + // item.Category = category; + // category.Children?.Add(item); + // } + // })); } - public new Task LoadAsync() { - return Task.Run(() => { + public async Task LoadAsync() { + await categoryCtx.LoadAsync(); + await bookmarkCtx.LoadAsync(); + await Task.Run(() => { try { - ConnectionClient.DbMaintenance.CreateDatabase(); - ConnectionClient.CodeFirst.InitTables(); - itemSugarCache.LoadDatabase(); - categorySugarCache.LoadDatabase(); - - if (categorySugarCache.Count() == 0) { + if (categoryCtx.Count() == 0) { var defaultCategory = new BookmarkCategory("DefaultBookmark".L()); - categorySugarCache.Add(defaultCategory); - categorySugarCache.Save(); - itemSugarCache.Add(new BookmarkItem(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Documents".L(), defaultCategory)); - itemSugarCache.Add(new BookmarkItem(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Desktop".L(), defaultCategory)); - itemSugarCache.Save(); + categoryCtx.Add(defaultCategory); + categoryCtx.Save(); + bookmarkCtx.Add(new BookmarkItem(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Documents".L(), defaultCategory)); + bookmarkCtx.Add(new BookmarkItem(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Desktop".L(), defaultCategory)); + bookmarkCtx.Save(); } } catch (Exception e) { MessageBox.Show("无法加载数据库,可能是权限不够或者数据库版本过旧,请删除Data文件夹后再试一次。\n错误为:" + e.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); @@ -49,54 +45,26 @@ public BookmarkSugarContext() : base("BookMarks.db") { }); } - public void Add(BookmarkItem bookmark) { - itemSugarCache.Add(bookmark); - } + public void Add(BookmarkItem bookmark) => bookmarkCtx.Add(bookmark); - public void Add(BookmarkCategory category) { - categorySugarCache.Add(category); - } + public void Add(BookmarkCategory category) => categoryCtx.Add(category); - public Task AddAsync(BookmarkItem bookmark) { - return Task.Run(() => Add(bookmark)); - } + public bool Contains(BookmarkItem bookmark) => bookmarkCtx.Contains(bookmark); - public Task AddAsync(BookmarkCategory category) { - return Task.Run(() => Add(category)); - } + public bool Any(Expression> match) => bookmarkCtx.Any(match); - public bool Contains(BookmarkItem bookmark) { - return itemSugarCache.Contains(bookmark); - } + public BookmarkCategory? FirstOrDefault(Expression> match) => categoryCtx.FirstOrDefault(match); - public bool Any(Func match) { - return itemSugarCache.Any(match); - } - - public BookmarkCategory? FirstOrDefault(Func match) { - return categorySugarCache.FirstOrDefault(match); - } - - public BookmarkItem? FirstOrDefault(Func match) { - return itemSugarCache.FirstOrDefault(match); - } + public BookmarkItem? FirstOrDefault(Expression> match) => bookmarkCtx.FirstOrDefault(match); - public ObservableCollection GetBindable() { - var ret = new ObservableCollection(); - categorySugarCache.ForEach(x => ret.Add(x)); - return ret; - } + public ObservableCollection GetBindable() => categoryCtx.GetBindable(); - public void Remove(BookmarkItem bookmark) { - itemSugarCache.Remove(bookmark); - } + public void Remove(BookmarkItem bookmark) => bookmarkCtx.Remove(bookmark); - public override void Save() { - itemSugarCache.Save(); - categorySugarCache.Save(); + public void Save() { + categoryCtx.Save(); + bookmarkCtx.Save(); } - public override Task SaveAsync() { - return Task.Run(Save); - } + public Task SaveAsync() => Task.Run(Save); // TODO: Thread safe??? } \ No newline at end of file diff --git a/ExplorerEx/Database/SqlSugar/FileViewSugarContext.cs b/ExplorerEx/Database/SqlSugar/FileViewSugarContext.cs index 971efe7..77f7881 100644 --- a/ExplorerEx/Database/SqlSugar/FileViewSugarContext.cs +++ b/ExplorerEx/Database/SqlSugar/FileViewSugarContext.cs @@ -1,50 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using ExplorerEx.Database.Interface; +using ExplorerEx.Database.Interface; using ExplorerEx.Model; namespace ExplorerEx.Database.SqlSugar; -public class FileViewSugarContext : SugarContext, IFileViewDbContext { - private readonly SugarCache fileSugarCache; +/// +/// 这个不需要Cache +/// +public class FileViewSugarContext : SugarContext, IFileViewDbContext { + public FileViewSugarContext() : base("FileViews.db") { } - public FileViewSugarContext() : base("FileViews.db") { - fileSugarCache = new SugarCache(ConnectionClient); - } - - public override Task LoadAsync() { - return Task.Run(() => { - ConnectionClient.CodeFirst.InitTables(); - fileSugarCache.LoadDatabase(); - }); - } - - public bool Any(Func match) { - throw new NotImplementedException(); - } - - public void Add(FileView item) { - fileSugarCache.Add(item); - } - - public Task AddAsync(FileView item) { - return Task.Run(() => Add(item)); - } - - public FileView? FirstOrDefault(Func match) { - return fileSugarCache.FirstOrDefault(match); - } - - public bool Contains(FileView fileView) { - return fileSugarCache.Contains(fileView); - } - - public override void Save() { - fileSugarCache.Save(); - } - - public override Task SaveAsync() { - return Task.Run(Save); + public void Update(FileView item) { + ConnectionClient.Updateable().Where(i => i.FullPath == item.FullPath); } } \ No newline at end of file diff --git a/ExplorerEx/Database/SqlSugar/SqlSugarHelper.cs b/ExplorerEx/Database/SqlSugar/SqlSugarHelper.cs new file mode 100644 index 0000000..56634bd --- /dev/null +++ b/ExplorerEx/Database/SqlSugar/SqlSugarHelper.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using SqlSugar; + +namespace ExplorerEx.Database.SqlSugar; + +/// +/// 实验性质 +/// +public static class SqlSugarHelper { + private static readonly Dictionary TypeCache; + private static readonly MethodInfo GetCustomTypeByClassInfo; + + static SqlSugarHelper() { + TypeCache = (Dictionary)typeof(InstanceFactory).GetField("typeCache", BindingFlags.NonPublic | BindingFlags.Static)!.GetValue(null)!; + GetCustomTypeByClassInfo = typeof(InstanceFactory).GetMethod("GetCustomTypeByClass", BindingFlags.NonPublic | BindingFlags.Static)!; + } + + public static object Queryable(this SqlSugarClient client, Type type) { + client.InitMappingInfo(type); + return GetCacheInstance(GetClassName(client.CurrentConnectionConfig.DbType.ToString(), "Queryable"), type); + } + + private static string GetClassName(string type, string name) { + return type switch { + "MySqlConnector" => "SqlSugar.MySqlConnector.MySql" + name, + "Access" => "SqlSugar.Access.Access" + name, + "ClickHouse" => "SqlSugar.ClickHouse.ClickHouse" + name, + _ => type == "Custom" ? InstanceFactory.CustomNamespace + "." + InstanceFactory.CustomDbName + name : "SqlSugar." + type + name + }; + } + + private static object GetCacheInstance(string className, Type type) { + var key = className + type; + lock (TypeCache) { + if (TypeCache.ContainsKey(key)) { + type = TypeCache[key]; + } else { + if (string.IsNullOrEmpty(InstanceFactory.CustomDllName)) { + type = Type.GetType(className + "`1", true)!.MakeGenericType(type); + } else { + var customTypeByClass = (Type?)GetCustomTypeByClassInfo.Invoke(null, new object[] { className + "`1" }); + if (customTypeByClass != null) { + type = customTypeByClass.MakeGenericType(type); + } + } + if (!TypeCache.ContainsKey(key)) { + TypeCache.Add(key, type); + } + } + } + return Activator.CreateInstance(type, true)!; + } +} \ No newline at end of file diff --git a/ExplorerEx/Database/SqlSugar/SugarCache.cs b/ExplorerEx/Database/SqlSugar/SugarCache.cs index 953d56a..f47d227 100644 --- a/ExplorerEx/Database/SqlSugar/SugarCache.cs +++ b/ExplorerEx/Database/SqlSugar/SugarCache.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Runtime.InteropServices; using Castle.DynamicProxy; using SqlSugar; @@ -19,13 +19,9 @@ public abstract class SugarCacheBase { protected bool IsLoaded; protected SugarCacheBase(Type type) { - if (!type.IsClass || Caches.ContainsKey(type)) { - throw new ExternalException("This class has already been proxied"); - } + Debug.Assert(!type.IsClass || Caches.ContainsKey(type)); Caches.Add(type, this); } - - public abstract void LoadDatabase(); } /// @@ -75,7 +71,7 @@ public SugarCache(ISqlSugarClient db) : base(typeof(T)) { interceptor = new DbModelInterceptor(this); } - public override void LoadDatabase() { + public void LoadDatabase() { if (!IsLoaded) { var interceptor = new DbModelInterceptor(this); db.Queryable().ForEach(x => locals.Add(Generator.CreateClassProxyWithTarget(x, interceptor))); @@ -83,13 +79,9 @@ public override void LoadDatabase() { } } - public T? FirstOrDefault(Func match) { - return locals.FirstOrDefault(match); - } + public T? FirstOrDefault(Func match) => locals.FirstOrDefault(match); - public bool Contains(T item) { - return locals.Contains(item); - } + public bool Contains(T item) => locals.Contains(item); public void ForEach(Action action) { foreach (var local in locals) { @@ -97,9 +89,7 @@ public void ForEach(Action action) { } } - public bool Any(Func match) { - return locals.Any(match); - } + public bool Any(Func match) => locals.Any(match); public void Add(T item) { // TODO: 目前是加锁,是否需要一个并行方法?(并行参考Utils.Collections.ConcurrentObservableCollection) @@ -110,6 +100,18 @@ public void Add(T item) { } } + /// + /// 标记为已更改 + /// + /// 为Proxy + private void MarkAsChanged(T item) { + lock (lockObj) { + if (!changes.Contains(item) && !deletes.Contains(item) && locals.Contains(item)) { + changes.Add(item); + } + } + } + public void Remove(T item) { lock (lockObj) { if (!adds.Remove(item) && locals.Remove(item)) { @@ -119,9 +121,7 @@ public void Remove(T item) { } } - public int Count() { - return locals.Count; - } + public int Count() => locals.Count; public void Save() { lock (lockObj) { @@ -158,71 +158,14 @@ public void Intercept(IInvocation invocation) { // 这里只用一个简单的比对即可 // 至于下面的Contains和Add,目前是HashSet,性能很高 // 可以考虑使用并发Add + if (invocation.Method.Attributes.HasFlag(MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.SpecialName)) { - var p = (T)invocation.Proxy; - if (!cache.changes.Contains(p) && !cache.deletes.Contains(p) && cache.locals.Contains(p)) { - cache.changes.Add(p); + if (invocation.Method.ReturnType == typeof(void)) { + cache.MarkAsChanged((T)invocation.Proxy); // set + } else { + } } } } } - -/// -/// 含有依赖关系的缓存 -/// -/// -/// -public class SugarCache : SugarCache - where TSelf : class, new() - where TSub : class, new() { - - private readonly SugarStrategy strategy; - - public SugarCache(ISqlSugarClient db, SugarStrategy strategy) : base(db) { - this.strategy = strategy; - } - - public void LoadDataBase() { - if (!IsLoaded) { - var interceptor = new DbModelInterceptor(this); - db.Queryable().ForEach(x => { - strategy.Source.ForEach(v => strategy.Action(x, v)); - locals.Add(Generator.CreateClassProxyWithTarget(x, interceptor)); - }); - IsLoaded = true; - } - } -} - -/// -/// 含有更多依赖关系的缓存 -/// -/// -/// -/// -public class SugarCache : SugarCache - where TSelf : class, new() - where TSub1 : class, new() - where TSub2 : class, new() { - private readonly SugarStrategy strategy1; - private readonly SugarStrategy strategy2; - - public SugarCache(ISqlSugarClient db, SugarStrategy strategy1, SugarStrategy strategy2) : base(db) { - this.strategy1 = strategy1; - this.strategy2 = strategy2; - } - - public void LoadDataBase() { - if (!IsLoaded) { - var interceptor = new DbModelInterceptor(this); - db.Queryable().ForEach(x => { - strategy1.Source.ForEach(v => strategy1.Action(x, v)); - strategy2.Source.ForEach(v => strategy2.Action(x, v)); - locals.Add(Generator.CreateClassProxyWithTarget(x, interceptor)); - }); - IsLoaded = true; - } - - } -} \ No newline at end of file diff --git a/ExplorerEx/Database/SqlSugar/SugarContext.cs b/ExplorerEx/Database/SqlSugar/SugarContext.cs index 33252a9..91b98d6 100644 --- a/ExplorerEx/Database/SqlSugar/SugarContext.cs +++ b/ExplorerEx/Database/SqlSugar/SugarContext.cs @@ -1,23 +1,23 @@ using System; -using System.Collections; +using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.Diagnostics; using System.IO; using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using ExplorerEx.Database.Interface; using ExplorerEx.Database.Shared; +using ExplorerEx.Model; using SqlSugar; namespace ExplorerEx.Database.SqlSugar; -public abstract class SugarContext : IDatabase { +public class SugarContext : IDatabase, IDbContext where T : class, new() { protected readonly SqlSugarClient ConnectionClient; - protected SugarContext(string databaseFilename) { - var path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "SqlSugar"); + public SugarContext(string databaseFilename) { + var path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Data"); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } @@ -70,25 +70,89 @@ protected SugarContext(string databaseFilename) { } }); // 实体创建的时候执行 - ConnectionClient.Aop.DataExecuting = (value, info) => { - var column = info.EntityColumnInfo; - if (column.Navigat != null) { - if (info.EntityValue is INotifyCollectionChanged ncc) { - - } + ConnectionClient.Aop.DataExecuting = (_, info) => Aop(info.EntityValue, info.EntityColumnInfo); + ConnectionClient.Aop.DataExecuted = (_, info) => { + foreach (var column in info.EntityColumnInfos) { + Aop(info.EntityValue, column); } }; } - public virtual Task LoadAsync() { - throw new InvalidOperationException(); + private void Aop(object entityValue, EntityColumnInfo column) { + // TODO: Fuck this, I'm out + if (column.Navigat != null) { + switch (column.Navigat.NavigatType) { + case NavigateType.OneToOne: + if (column.UnderType == typeof(BookmarkCategory)) { + var category = ConnectionClient.Queryable().First(c => c.Name == ((BookmarkItem)entityValue).Name); + column.PropertyInfo.SetValue(entityValue, category); + } + break; + case NavigateType.OneToMany: // 这里是一个集合,那么就需要查询一次,填充集合 + if (column.UnderType == typeof(ObservableCollection)) { + var list = ConnectionClient.Queryable() + .Where(i => i.CategoryForeignKey == ((BookmarkCategory)entityValue).Name) + .ToList(); + column.PropertyInfo.SetValue(entityValue, new ObservableCollection(list)); + } + break; + } + } } - public virtual void Save() { - throw new InvalidOperationException(); + public virtual Task LoadAsync() => Task.Run(() => { + ConnectionClient.DbMaintenance.CreateDatabase(); + ConnectionClient.CodeFirst.InitTables(); + }); + + public virtual void Save() => ConnectionClient.SaveQueues(); + + public virtual Task SaveAsync() => ConnectionClient.SaveQueuesAsync(); + + public virtual void Add(T item) => ConnectionClient.Insertable(item); + + public virtual T? FirstOrDefault(Expression> match) => ConnectionClient.Queryable().First(match); + + public virtual void Remove(T item) => ConnectionClient.Deleteable(item); + + public virtual bool Contains(T item) => ConnectionClient.Queryable().Any(i => i == item); + + public virtual bool Any(Expression> match) => ConnectionClient.Queryable().Any(match); + + public virtual int Count() => ConnectionClient.Queryable().Count(); +} + +public class CachedSugarContext : SugarContext where T : class, new() { + private readonly SugarCache cache; + + public CachedSugarContext(string databaseFilename) : base(databaseFilename) { + cache = new SugarCache(ConnectionClient); + } + + public override async Task LoadAsync() { + await base.LoadAsync(); + await Task.Run(cache.LoadDatabase); } - public virtual Task SaveAsync() { - throw new InvalidOperationException(); + public override void Save() => cache.Save(); + + public override Task SaveAsync() => Task.Run(cache.Save); // TODO: Thread safe??? + + public override void Add(T item) => cache.Add(item); + + public override T? FirstOrDefault(Expression> match) => cache.FirstOrDefault(match.Compile()); + + public override void Remove(T item) => cache.Remove(item); + + public override bool Contains(T item) => cache.Contains(item); + + public override bool Any(Expression> match) => cache.Any(match.Compile()); + + public override int Count() => cache.Count(); + + public ObservableCollection GetBindable() { + var list = new List(cache.Count()); // 初始化容量 + cache.ForEach(i => list.Add(i)); // 先使用List,这样Add的时候不会触发事件,减少消耗 + return new ObservableCollection(list); } } \ No newline at end of file diff --git a/ExplorerEx/Model/FileListViewItem/Bookmark.cs b/ExplorerEx/Model/FileListViewItem/Bookmark.cs index d4a8342..931174c 100644 --- a/ExplorerEx/Model/FileListViewItem/Bookmark.cs +++ b/ExplorerEx/Model/FileListViewItem/Bookmark.cs @@ -1,21 +1,11 @@ -// #define EFCore - -using System; -using System.Collections.Generic; +using System; using System.Collections.ObjectModel; -using System.Collections.Specialized; using System.IO; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Windows; using System.Windows.Media; using ExplorerEx.Converter; -using ExplorerEx.Database.Interface; using ExplorerEx.Database.Shared; using ExplorerEx.Utils; -using Microsoft.EntityFrameworkCore; using static ExplorerEx.Utils.IconHelper; -using Task = System.Threading.Tasks.Task; namespace ExplorerEx.Model; @@ -26,7 +16,7 @@ namespace ExplorerEx.Model; [DbTable(TableName = "BookmarkCategoryDbSet")] public class BookmarkCategory : NotifyPropertyChangedBase { [DbColumn(IsPrimaryKey = true)] - public string Name { get; set; } = null!; + public virtual string Name { get; set; } = null!; /// /// 在Binding里使用 @@ -56,7 +46,7 @@ public BookmarkCategory(string name) { } public void AddBookmark(BookmarkItem item) { - Children = new ObservableCollection(); + Children ??= new ObservableCollection(); Children.Add(item); OnPropertyChanged(nameof(Children)); OnPropertyChanged(nameof(Icon)); @@ -75,6 +65,9 @@ public override string ToString() { public class BookmarkItem : FileListViewItem, IFilterable { public override string DisplayText => Name; + /// + /// 不要设置这个的值 + /// [DbColumn] public virtual string CategoryForeignKey { get; set; } = null!; @@ -84,12 +77,13 @@ public class BookmarkItem : FileListViewItem, IFilterable { [DbColumn(nameof(CategoryForeignKey), DbNavigateType.OneToOne)] public virtual BookmarkCategory Category { get; set; } = null!; - public BookmarkItem() : base(null!, null!, false) { } + public BookmarkItem() { } - public BookmarkItem(string fullPath, string name, BookmarkCategory category) : base(null!, null!, false) { + public BookmarkItem(string fullPath, string name, BookmarkCategory category) { FullPath = Path.GetFullPath(fullPath); Name = name; Category = category; + CategoryForeignKey = category.Name; // 不管了先设置上 category.AddBookmark(this); } @@ -123,138 +117,4 @@ protected override bool InternalRename(string newName) { public bool Filter(string filter) { return Name.ToLower().Contains(filter); } -} - - -//public static class BookmarkManager { -// public static DbCollection BookmarkCategories { get; } -// public static DbCollection BookmarkItems { get; } - -// private static readonly IDatabase Database; - -// public static Task LoadDataBase() { -// return Database.LoadAsync(); -// } - -// #region EF Core - -// static BookmarkManager() { -// var dbContext = new EfCoreBookmarksDbContext(); -// Database = dbContext; -// BookmarkCategories = new EfCoreDbCollection(Database, dbContext.BookmarkCategoryDbSet); -// BookmarkItems = new EfCoreDbCollection(Database, dbContext.BookmarkDbSet); -// } - -// /// -// /// 对接到ef core -// /// -// /// -// public class EfCoreDbCollection : DbCollection where TEntity : class { -// private readonly DbSet dbSet; - -// public EfCoreDbCollection(IDatabase database, DbSet dbSet) : base(database) { -// this.dbSet = dbSet; -// } - -// private void DbSet_OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { -// if (e.Action is not NotifyCollectionChangedAction.Replace and not NotifyCollectionChangedAction.Move) { -// OnPropertyChanged(nameof(Count)); -// } -// OnCollectionChanged(e); -// } - -// public override void Add(TEntity item) { -// dbSet.Local.Add(item); -// } - -// public override void Clear() { -// dbSet.Local.Clear(); -// } - -// public override bool Contains(TEntity item) { -// return dbSet.Local.Contains(item); -// } - -// public override void CopyTo(TEntity[] array, int arrayIndex) { -// dbSet.Local.CopyTo(array, arrayIndex); -// } - -// public override bool Remove(TEntity item) { -// return dbSet.Local.Remove(item); -// } - -// [MethodImpl(MethodImplOptions.AggressiveInlining)] -// public override Task LoadAsync() { -// dbSet.Local.ToObservableCollection().CollectionChanged += DbSet_OnCollectionChanged; -// return dbSet.LoadAsync(); -// } - -// [MethodImpl(MethodImplOptions.AggressiveInlining)] -// public override Task SaveChangesAsync() { -// return database.SaveAsync(); -// } - -// public override IEnumerator GetEnumerator() { -// return dbSet.Local.GetEnumerator(); -// } - -// public override int Count => dbSet.Local.Count; - -// public override bool IsReadOnly => false; -// } - -// private class EfCoreBookmarksDbContext : DbContext, IDatabase { -// public DbSet BookmarkCategoryDbSet { get; set; } = null!; -// public DbSet BookmarkDbSet { get; set; } = null!; - -// private readonly string dbPath; - -// public EfCoreBookmarksDbContext() { -// var path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Data"); -// if (!Directory.Exists(path)) { -// Directory.CreateDirectory(path); -// } -// dbPath = Path.Combine(path, "BookMarks.db"); -// } - -// protected override void OnConfiguring(DbContextOptionsBuilder ob) { -// ob.UseSqlite($"Data Source={dbPath}"); -// } - -// protected override void OnModelCreating(ModelBuilder modelBuilder) { -// modelBuilder.Entity().HasOne(b => b.Category) -// .WithMany(cb => cb.Children).HasForeignKey(b => b.CategoryForeignKey); -// } - -// public async Task LoadAsync() { -// try { -// await Database.EnsureCreatedAsync(); -// await BookmarkCategoryDbSet.LoadAsync(); -// await BookmarkDbSet.LoadAsync(); -// if (BookmarkCategories.Count == 0) { -// var defaultCategory = new BookmarkCategory("DefaultBookmark".L()); -// BookmarkCategories.Add(defaultCategory); -// BookmarkItems.Add(new BookmarkItem(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Documents".L(), defaultCategory)); -// BookmarkItems.Add(new BookmarkItem(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "Desktop".L(), defaultCategory)); -// await BookmarkCategories.SaveChangesAsync(); -// await BookmarkItems.SaveChangesAsync(); -// } -// } catch (Exception e) { -// MessageBox.Show("无法加载数据库,可能是权限不够或者数据库版本过旧,请删除Data文件夹后再试一次。\n错误为:" + e.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error); -// Logger.Exception(e, false); -// } -// } - -// public void Save() { -// throw new NotImplementedException(); -// } - -// [MethodImpl(MethodImplOptions.AggressiveInlining)] -// public Task SaveAsync() { -// return ((DbContext)this).SaveChangesAsync(); -// } -// } - - -// #endregion -//} \ No newline at end of file +} \ No newline at end of file diff --git a/ExplorerEx/Model/FileListViewItem/FileListViewItem.cs b/ExplorerEx/Model/FileListViewItem/FileListViewItem.cs index 4584ec9..630cdcd 100644 --- a/ExplorerEx/Model/FileListViewItem/FileListViewItem.cs +++ b/ExplorerEx/Model/FileListViewItem/FileListViewItem.cs @@ -19,6 +19,8 @@ namespace ExplorerEx.Model; /// 所有可以显示在中的项目的基类 /// public abstract class FileListViewItem : INotifyPropertyChanged { + protected FileListViewItem() { } + protected FileListViewItem(string fullPath, string name, bool isFolder) { FullPath = fullPath; Name = name; @@ -70,7 +72,7 @@ public double Opacity { private double opacity = 1d; [DbColumn(IsPrimaryKey = true)] - public string FullPath { get; protected init; } + public virtual string FullPath { get; protected init; } public abstract string DisplayText { get; } @@ -124,10 +126,7 @@ public bool IsSelected { /// 加载文件的各项属性 /// public abstract void LoadAttributes(LoadDetailsOptions options); - - /// - /// LoadIcon用到了shell,那并不是一个可以多线程的方法,所以与其每次都Task.Run,不如提高粗粒度 - /// + public abstract void LoadIcon(LoadDetailsOptions options); #region 文件重命名 diff --git a/ExplorerEx/Model/FileListViewItem/FileSystemItem.cs b/ExplorerEx/Model/FileListViewItem/FileSystemItem.cs index d9236c9..f3489e1 100644 --- a/ExplorerEx/Model/FileListViewItem/FileSystemItem.cs +++ b/ExplorerEx/Model/FileListViewItem/FileSystemItem.cs @@ -119,6 +119,7 @@ public override void LoadAttributes(LoadDetailsOptions options) { if (FileSystemInfo == null) { return; } + FileSystemInfo.Refresh(); var type = FileUtils.GetFileTypeDescription(FileSystemInfo.Extension); if (string.IsNullOrEmpty(type)) { Type = "UnknownType".L(); @@ -215,13 +216,17 @@ protected static ImageSource InitializeIsEmptyFolder(string fullPath) { public override string DisplayText => Name; public override void LoadAttributes(LoadDetailsOptions options) { + if (FileSystemInfo == null) { + return; + } isEmptyFolder = FolderUtils.IsEmptyFolder(FullPath); IsEmptyFolderDictionary.Add(FullPath, isEmptyFolder); Type = isEmptyFolder ? "EmptyFolder".L() : "Folder".L(); - var directoryInfo = new DirectoryInfo(FullPath); + var directoryInfo = (DirectoryInfo)FileSystemInfo; + directoryInfo.Refresh(); DateModified = directoryInfo.LastWriteTime; DateCreated = directoryInfo.CreationTime; - if (FileSystemInfo != null && FileSystemInfo.Attributes.HasFlag(FileAttributes.Hidden)) { + if (directoryInfo.Attributes.HasFlag(FileAttributes.Hidden)) { Opacity = 0.5d; } } diff --git a/ExplorerEx/Model/FileListViewItem/FolderOnlyItem.cs b/ExplorerEx/Model/FileListViewItem/FolderOnlyItem.cs index 81dd675..4c11ffe 100644 --- a/ExplorerEx/Model/FileListViewItem/FolderOnlyItem.cs +++ b/ExplorerEx/Model/FileListViewItem/FolderOnlyItem.cs @@ -27,10 +27,19 @@ internal sealed class FolderOnlyItem : FileListViewItem { /// 充当占位项目,以便展开 /// public static readonly FolderOnlyItem DefaultItem = new(Home) { + Name = "Loading".L() + }; + + /// + /// 充当占位项目,以便展开 + /// + public static readonly FolderOnlyItem EmptyItem = new(Home) { Name = "EmptyFolder".L() }; - private static readonly ReadOnlyCollection DefaultChildren = new(new List { DefaultItem }); + private static readonly ReadOnlyCollection LoadingChildren = new(new List { DefaultItem }); + + private static readonly ReadOnlyCollection EmptyChildren = new(new List { EmptyItem }); public override string DisplayText => Name; @@ -89,7 +98,8 @@ public FolderOnlyItem(string zipPath, string relativePath, FolderOnlyItem parent public FolderOnlyItem(DirectoryInfo directoryInfo, FolderOnlyItem parent) : base(directoryInfo.FullName, directoryInfo.Name, true) { Parent = parent; IsFolder = true; - if (Directory.EnumerateDirectories(FullPath).Any()) { + // 只看有没有文件夹,不能用FolderUtils.IsEmptyFolder + if (Directory.EnumerateDirectories(FullPath).Any() || Directory.EnumerateFiles(FullPath, "*.zip").Any()) { InitializeChildren(); } } @@ -104,7 +114,7 @@ public FolderOnlyItem(DriveInfo driveInfo): base(driveInfo.Name, DriveUtils.GetF } private void InitializeChildren() { - Children = DefaultChildren; + Children = LoadingChildren; actualChildren = new ConcurrentObservableCollection(); } @@ -139,7 +149,7 @@ protected override bool InternalRename(string newName) { public FolderOnlyItem Parent { get; } /// - /// 枚举之前,先把这个设为,枚举完成后如数量大于1,设为 + /// 枚举之前,先把这个设为,枚举完成后如数量大于1,设为 /// public IList? Children { get => children; @@ -173,7 +183,7 @@ public bool IsExpanded { } cts?.Cancel(); - Children = DefaultChildren; + Children = LoadingChildren; if (actualChildren == null) { actualChildren = new ConcurrentObservableCollection(); } else { @@ -263,8 +273,12 @@ public bool IsExpanded { } item.LoadIcon(LoadDetailsOptions.Default); } + } else { + Children = EmptyChildren; } }, token); + } else { + actualChildren?.Clear(); // 释放内存 } } } @@ -285,7 +299,7 @@ public void UpdateDriveChildren() { cts = new CancellationTokenSource(); var token = cts.Token; Task.Run(() => { - Children = DefaultChildren; + Children = LoadingChildren; if (actualChildren == null) { actualChildren = new ConcurrentObservableCollection(); } else { diff --git a/ExplorerEx/Model/FileView.cs b/ExplorerEx/Model/FileView.cs index b1094d5..733d85f 100644 --- a/ExplorerEx/Model/FileView.cs +++ b/ExplorerEx/Model/FileView.cs @@ -420,39 +420,6 @@ private void StageChange([CallerMemberName] string propertyName = null!) { } } - /// - /// 与另一个FileView对比,将差异暂存 - /// - /// - public void StageChangesFromOther(FileView other) { - if (fullPath != other.fullPath) { - StageChange(nameof(FullPath)); - } - if (pathType != other.pathType) { - StageChange(nameof(PathType)); - } - if (sortBy != other.sortBy) { - StageChange(nameof(SortBy)); - } - if (isAscending != other.isAscending) { - StageChange(nameof(IsAscending)); - } - if (groupBy != other.groupBy) { - StageChange(nameof(GroupBy)); - } - if (fileViewType != other.fileViewType) { - StageChange(nameof(FileViewType)); - } - if (ItemSize != other.ItemSize) { - StageChange(nameof(ItemSize)); - } - if ((DetailListsData != null && other.DetailListsData == null) || - (DetailListsData == null && other.DetailListsData != null) || - (DetailListsData != null && other.DetailListsData != null && !DetailListsData.SequenceEqual(other.DetailListsData))) { - StageChange(nameof(DetailListsData)); - } - } - /// /// 将所有属性标记为已更改 /// diff --git a/ExplorerEx/Settings.cs b/ExplorerEx/Settings.cs index 98095ef..8be16ec 100644 --- a/ExplorerEx/Settings.cs +++ b/ExplorerEx/Settings.cs @@ -2,11 +2,15 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Globalization; +using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Media; +using System.Windows.Media.Imaging; using System.Xml; +using ExplorerEx.Command; using ExplorerEx.Utils; using Microsoft.Win32; @@ -47,7 +51,7 @@ public SettingsExpander(string header, string? description, string? icon) : base internal enum SettingsType { Unknown, Boolean, - Integer, + Number, String, Select, } @@ -77,7 +81,7 @@ public object? Value { OnPropertyChanged(); OnPropertyChanged(nameof(Self)); if (value != null) { - ConfigHelper.Save(FullName, value); + ConfigHelper.SaveToBuffer(FullName, value); } else { ConfigHelper.Delete(FullName); } @@ -93,18 +97,22 @@ public object? Value { /// public SettingsItem Self { get; } - public object? Default { get; set; } + public virtual void SetDefaultValue(object? value) { + Value = value; + } public bool GetBoolean() => Convert.ToBoolean(Value); public int GetInt32() => Convert.ToInt32(Value); - public string GetString() => Convert.ToString(Value) ?? Convert.ToString(Default) ?? string.Empty; + public double GetDouble() => Convert.ToDouble(Value); + + public string GetString() => Convert.ToString(Value) ?? string.Empty; /// /// 与不同,这个是专门用于监测值的变化 /// - public event Action? Changed; + public event Action? Changed; public event PropertyChangedEventHandler? PropertyChanged; @@ -129,21 +137,69 @@ public override string ToString() { } } + public List Items { get; } = new(); + + public int SelectedIndex { + get => Value is int value ? Items.FindIndex(i => i.Value == value) : -1; + set { + if (value < 0 || value >= Items.Count) { + Value = null; + } else { + Value = Items[value].Value; + } + } + } + public SettingsSelectItem(string fullName, string header, string? description, string? icon) : base(fullName, header, description, icon, SettingsType.Select) { } - public List Items { get; } = new(); + public override void SetDefaultValue(object? value) { + if (value is int i) { + Value = i; + } else { + Value = 0; + } + } } internal class SettingsBooleanItem : SettingsItem { public SettingsBooleanItem(string fullName, string header, string? description, string? icon) : base(fullName, header, description, icon, SettingsType.Boolean) { } } -internal class SettingsIntegerItem : SettingsItem { - public SettingsIntegerItem(string fullName, string header, string? description, string? icon) : base(fullName, header, description, icon, SettingsType.Integer) { } +internal class SettingsNumberItem : SettingsItem { + public double Min { get; } + + public double Max { get; } + + public SettingsNumberItem(string fullName, string header, double min, double max, string? description, string? icon) : base(fullName, header, description, icon, SettingsType.Number) { + Min = min; + Max = max; + } + + public override void SetDefaultValue(object? value) { + if (value != null && double.TryParse(value.ToString(), out var number) && number <= Max && number >= Min) { + Value = number; + } else { + Value = Max; + } + } } internal class SettingsStringItem : SettingsItem { + public SimpleCommand? BrowserFileCommand { get; set; } + public SettingsStringItem(string fullName, string header, string? description, string? icon) : base(fullName, header, description, icon, SettingsType.String) { } + + public void SetBrowserFileCommand(string filter) { + BrowserFileCommand = new SimpleCommand(() => { + var ofd = new OpenFileDialog { + CheckFileExists = true, + Filter = filter + }; + if (ofd.ShowDialog().GetValueOrDefault()) { + Value = ofd.FileName; + } + }); + } } public enum ColorMode { @@ -158,7 +214,7 @@ public enum WindowBackdrop { Mica } -internal sealed class Settings { +internal sealed class Settings : INotifyPropertyChanged { public static Settings Current { get; } = new(); public ObservableCollection Categories { get; } = new(); @@ -168,18 +224,24 @@ internal sealed class Settings { #region 特殊/常用设置 public static class CommonSettings { - public const string ColorMode = "Appearance.Theme.ColorMode"; - public const string WindowBackdrop = "Appearance.Theme.WindowBackdrop"; + public const string Language = "Appearance.Language"; + public const string ColorMode = "Appearance.ColorMode"; + public const string WindowBackdrop = "Appearance.Background.WindowBackdrop"; + public const string BackgroundImage = "Appearance.Background.BackgroundImage"; + public const string BackgroundImageOpacity = "Appearance.Background.BackgroundImageOpacity"; public const string DoubleClickGoUpperLevel = "Common.DoubleClickGoUpperLevel"; + public const string DontAskWhenClosingMultiTabs = "Customize.DontAskWhenClosingMultiTabs"; public const string DontAskWhenRecycle = "Customize.DontAskWhenRecycle"; public const string DontAskWhenDelete = "Customize.DontAskWhenDelete"; public const string ShowHiddenFilesAndFolders = "Advanced.ShowHiddenFilesAndFolders"; public const string ShowProtectedSystemFilesAndFolders = "Advanced.ShowProtectedSystemFilesAndFolders"; } - + + public static CultureInfo CurrentCulture { get; private set; } = CultureInfo.CurrentUICulture; + public static event Action? ThemeChanged; /// @@ -208,13 +270,40 @@ public bool IsDarkMode { public WindowBackdrop WindowBackdrop => (WindowBackdrop)settings[CommonSettings.WindowBackdrop].GetInt32(); + public ImageSource? BackgroundImage { get; set; } + + public double BackgroundImageOpacity => this[CommonSettings.BackgroundImageOpacity].GetDouble(); + private void RegisterEvents() { ThemeChanged = null; - settings[CommonSettings.ColorMode].Changed += _ => ThemeChanged?.Invoke(); + settings[CommonSettings.Language].Changed += o => { // 更改界面语言 + if (o is int lcId) { + var prevCulture = CurrentCulture; + try { + CurrentCulture = new CultureInfo(lcId); + } catch { + CurrentCulture = prevCulture; + } + } + }; settings[CommonSettings.WindowBackdrop].Changed += _ => ThemeChanged?.Invoke(); + settings[CommonSettings.BackgroundImage].Changed += o => { + if (o is string path && File.Exists(path)) { + try { + BackgroundImage = new BitmapImage(new Uri(path)); + } catch { + BackgroundImage = null; + } + } else { + BackgroundImage = null; + } + OnPropertyChanged(nameof(BackgroundImage)); + }; + settings[CommonSettings.BackgroundImageOpacity].Changed += _ => OnPropertyChanged(nameof(BackgroundImageOpacity)); + settings[CommonSettings.ColorMode].Changed += _ => ThemeChanged?.Invoke(); // 更改颜色 } - + #endregion public SettingsItem this[string name] { @@ -227,6 +316,11 @@ public SettingsItem this[string name] { } public void LoadSettings() { + var lcId = ConfigHelper.LoadInt(CommonSettings.Language, -1); + if (lcId != -1) { + CurrentCulture = new CultureInfo(lcId); + } + using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ExplorerEx.Assets.Settings.xml")!; using var xml = XmlReader.Create(stream); if (!xml.Read() || xml.Name != "settings") { @@ -272,8 +366,14 @@ public void LoadSettings() { case SettingsType.Boolean: item = new SettingsBooleanItem(fullName, header, xml.GetAttribute("description"), xml.GetAttribute("icon")); break; - case SettingsType.Integer: - item = new SettingsIntegerItem(fullName, header, xml.GetAttribute("description"), xml.GetAttribute("icon")); + case SettingsType.Number: + if (!double.TryParse(xml.GetAttribute("min"), out var min)) { + min = 0; + } + if (!double.TryParse(xml.GetAttribute("max"), out var max)) { + max = 0; + } + item = new SettingsNumberItem(fullName, header, min, max, xml.GetAttribute("description"), xml.GetAttribute("icon")); break; case SettingsType.String: item = new SettingsStringItem(fullName, header, xml.GetAttribute("description"), xml.GetAttribute("icon")); @@ -284,8 +384,7 @@ public void LoadSettings() { default: continue; } - item.Default = xml.GetAttribute("default"); - item.Value = ConfigHelper.Load(fullName) ?? xml.GetAttribute("default"); + item.SetDefaultValue(ConfigHelper.Load(fullName) ?? xml.GetAttribute("default")); if (expander != null) { expander.Items.Add(item); } else { @@ -294,14 +393,44 @@ public void LoadSettings() { settings.Add(fullName, item); } break; - case "option": + case "option": { if (xml.NodeType == XmlNodeType.Element && item is SettingsSelectItem ssi && (header = xml.GetAttribute("header")) != null) { - ssi.Items.Add(new SettingsSelectItem.Item(header, ssi.Items.Count)); + if (!int.TryParse(xml.GetAttribute("value"), out var value)) { + value = ssi.Items.Count; + } + ssi.Items.Add(new SettingsSelectItem.Item(header, value)); + ssi.Value ??= ssi.Items[0]; + } + break; + } + case "browse": { + if (xml.NodeType == XmlNodeType.Element && item is SettingsStringItem ssi) { + ssi.SetBrowserFileCommand(xml.GetAttribute("filter") ?? "*.*|*.*"); } break; } + } + } + + // 调整语言 + if (lcId == -1) { + settings[CommonSettings.Language].Value = CurrentCulture.LCID; + } + // 设置窗口背景材质 + if (ConfigHelper.Load(CommonSettings.WindowBackdrop) == null) { + if (Environment.OSVersion.Version >= Version.Parse("10.0.22000.0")) { + this[CommonSettings.WindowBackdrop].Value = 2; // Mica + } else { + this[CommonSettings.WindowBackdrop].Value = 1; // Acrylic + } } RegisterEvents(); } + + public event PropertyChangedEventHandler? PropertyChanged; + + private void OnPropertyChanged([CallerMemberName] string? propertyName = null) { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } } \ No newline at end of file diff --git a/ExplorerEx/Strings/Resources.Designer.cs b/ExplorerEx/Strings/Resources.Designer.cs index 481ed41..51c5bdf 100644 --- a/ExplorerEx/Strings/Resources.Designer.cs +++ b/ExplorerEx/Strings/Resources.Designer.cs @@ -87,6 +87,15 @@ internal static string ___FreeOf___ { } } + /// + /// 查找类似 共{0}个项目 的本地化字符串。 + /// + internal static string ___Item { + get { + return ResourceManager.GetString("...Item", resourceCulture); + } + } + /// /// 查找类似 共{0}个项目 的本地化字符串。 /// @@ -135,6 +144,15 @@ internal static string _AreYouSureToRecycleTheseFiles { } } + /// + /// 查找类似 设置文件浏览列表的背景图片 的本地化字符串。 + /// + internal static string _BackgroundImageDescription { + get { + return ResourceManager.GetString("#BackgroundImageDescription", resourceCulture); + } + } + /// /// 查找类似 无法格式化所选驱动器。 的本地化字符串。 /// @@ -231,6 +249,15 @@ internal static string _InvalidFileName { } } + /// + /// 查找类似 选择ExplorerEx的显示语言 的本地化字符串。 + /// + internal static string _LanguageDescription { + get { + return ResourceManager.GetString("#LanguageDescription", resourceCulture); + } + } + /// /// 查找类似 路径不存在或者没有权限访问。 的本地化字符串。 /// @@ -303,16 +330,6 @@ internal static string _ZipFileIsExecutable { } } - /// - /// 查找类似 要访问zip的内容,路径应该以“\”结尾。 - ///如C:\compress.zip\Folder1\ 的本地化字符串。 - /// - internal static string _ZipMustEndsWithSlash { - get { - return ResourceManager.GetString("#ZipMustEndsWithSlash", resourceCulture); - } - } - /// /// 查找类似 拒绝访问 的本地化字符串。 /// @@ -403,6 +420,33 @@ internal static string AvailableSpace { } } + /// + /// 查找类似 背景 的本地化字符串。 + /// + internal static string Background { + get { + return ResourceManager.GetString("Background", resourceCulture); + } + } + + /// + /// 查找类似 背景图片 的本地化字符串。 + /// + internal static string BackgroundImage { + get { + return ResourceManager.GetString("BackgroundImage", resourceCulture); + } + } + + /// + /// 查找类似 背景图片透明度 的本地化字符串。 + /// + internal static string BackgroundImageOpacity { + get { + return ResourceManager.GetString("BackgroundImageOpacity", resourceCulture); + } + } + /// /// 查找类似 回到首页 的本地化字符串。 /// @@ -502,6 +546,15 @@ internal static string CheckYourInputAndTryAgain { } } + /// + /// 查找类似 中文 的本地化字符串。 + /// + internal static string Chinese { + get { + return ResourceManager.GetString("Chinese", resourceCulture); + } + } + /// /// 查找类似 选择其他应用 的本地化字符串。 /// @@ -718,6 +771,15 @@ internal static string Documents { } } + /// + /// 查找类似 当关闭有多个标签页的窗口时不要提醒我 的本地化字符串。 + /// + internal static string DontAskWhenClosingMultiTabs { + get { + return ResourceManager.GetString("DontAskWhenClosingMultiTabs", resourceCulture); + } + } + /// /// 查找类似 (不推荐开启)永久删除文件时不提示确认 的本地化字符串。 /// @@ -862,6 +924,15 @@ internal static string EmptyRecycleBin { } } + /// + /// 查找类似 英文 的本地化字符串。 + /// + internal static string English { + get { + return ResourceManager.GetString("English", resourceCulture); + } + } + /// /// 查找类似 错误 的本地化字符串。 /// @@ -1087,6 +1158,15 @@ internal static string InitializeLoggerFailed { } } + /// + /// 查找类似 语言 的本地化字符串。 + /// + internal static string Language { + get { + return ResourceManager.GetString("Language", resourceCulture); + } + } + /// /// 查找类似 大 的本地化字符串。 /// @@ -1610,7 +1690,7 @@ internal static string ShowMoreOptions { } /// - /// 查找类似 显示受保护的系统文件和文件夹 的本地化字符串。 + /// 查找类似 (不推荐开启)显示受保护的系统文件和文件夹 的本地化字符串。 /// internal static string ShowProtectedSystemFilesAndFolders { get { @@ -1871,7 +1951,7 @@ internal static string View { } /// - /// 查找类似 窗口背景 的本地化字符串。 + /// 查找类似 窗口背景材质 的本地化字符串。 /// internal static string WindowBackdrop { get { diff --git a/ExplorerEx/Strings/Resources.en.resx b/ExplorerEx/Strings/Resources.en.resx index 0e88624..85b6b88 100644 --- a/ExplorerEx/Strings/Resources.en.resx +++ b/ExplorerEx/Strings/Resources.en.resx @@ -391,10 +391,6 @@ If the location in on this PC, make sure the device or drive is connected or the {0} You can find the detailed messages in the ExplorerEx\Logs directory. - - - To access the contents of the zip, the path should end with "\". -For example: C:\compress.zip\Folder1\ Cancel @@ -615,7 +611,7 @@ For the application to run properly, it's recommanded that you first extract all Show hidden files and folders - Show protected system files and folders + (Not recommanded) Show protected system files and folders Advanced @@ -627,6 +623,36 @@ For the application to run properly, it's recommanded that you first extract all Dont ask when move files to recycle bin - (Not recommanded to enable) Dont ask when delete files forever + (Not recommanded) Dont ask when delete files permanently + + + Background image + + + Set the background image of file browser list + + + Background image opacity + + + Background + + + Language + + + Select the interface language of ExplorerEx + + + Chinese + + + English + + + {0} item + + + Don't remind me when closing a window with multiple tabs \ No newline at end of file diff --git a/ExplorerEx/Strings/Resources.resx b/ExplorerEx/Strings/Resources.resx index defc06c..9e54768 100644 --- a/ExplorerEx/Strings/Resources.resx +++ b/ExplorerEx/Strings/Resources.resx @@ -440,10 +440,6 @@ ExplorerEx [-h|--help] [-r|--runInBackground] [-o|--openInNewWindow] [-d|--requi ExplorerEx遇到严重错误,需要立即退出。 {0} 您可以在 ExplorerEx安装目录\Logs 目录下找到详细信息。 - - - 要访问zip的内容,路径应该以“\”结尾。 -如C:\compress.zip\Folder1\ 取消 @@ -608,7 +604,7 @@ ExplorerEx [-h|--help] [-r|--runInBackground] [-o|--openInNewWindow] [-d|--requi 主题 - 窗口背景 + 窗口背景材质 纯色 @@ -662,7 +658,7 @@ ExplorerEx [-h|--help] [-r|--runInBackground] [-o|--openInNewWindow] [-d|--requi 显示隐藏的文件和文件夹 - 显示受保护的系统文件和文件夹 + (不推荐开启)显示受保护的系统文件和文件夹 高级 @@ -676,4 +672,34 @@ ExplorerEx [-h|--help] [-r|--runInBackground] [-o|--openInNewWindow] [-d|--requi (不推荐开启)永久删除文件时不提示确认 + + 背景图片 + + + 设置文件浏览列表的背景图片 + + + 背景图片透明度 + + + 背景 + + + 语言 + + + 选择ExplorerEx的显示语言 + + + 中文 + + + 英文 + + + 共{0}个项目 + + + 当关闭有多个标签页的窗口时不要提醒我 + \ No newline at end of file diff --git a/ExplorerEx/Utils/LangHelper.cs b/ExplorerEx/Utils/LangHelper.cs index 027f324..99fc400 100644 --- a/ExplorerEx/Utils/LangHelper.cs +++ b/ExplorerEx/Utils/LangHelper.cs @@ -6,7 +6,7 @@ namespace ExplorerEx.Utils; -internal static class LangHselper { +internal static class LangHelper { /// /// 本地化字符串 /// @@ -14,7 +14,7 @@ internal static class LangHselper { /// public static string L(this string key) { try { - return Resources.ResourceManager.GetString(key) ?? key; + return Resources.ResourceManager.GetString(key, Settings.CurrentCulture) ?? key; } catch { return key; } diff --git a/ExplorerEx/View/Controls/ContentDialog.xaml b/ExplorerEx/View/Controls/ContentDialog.xaml index 93b26a8..eef1d07 100644 --- a/ExplorerEx/View/Controls/ContentDialog.xaml +++ b/ExplorerEx/View/Controls/ContentDialog.xaml @@ -6,27 +6,29 @@ xmlns:ct="clr-namespace:ExplorerEx.View.Controls" mc:Ignorable="d" KeyboardNavigation.TabNavigation="Cycle" Background="#aa000000" SnapsToDevicePixels="True" UseLayoutRounding="True" Opacity="0" + MouseLeftButtonDown="ContentDialog_OnMouseLeftButtonDown" d:DataContext="{d:DesignInstance ct:ContentDialog}"> + Effect="{StaticResource EffectShadow4}"> 0.5,0.5 - + - - + - + + - + + - + private bool isDataContextChanging; - /// - /// 发生在切换标签页的时候 - /// - /// - /// - private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) { - isDataContextChanging = true; - var oldViewModel = e.OldValue as FileTabViewModel; - if (oldViewModel != null) { - oldViewModel.ScrollViewX = scrollViewer!.HorizontalOffset; - oldViewModel.ScrollViewY = scrollViewer!.VerticalOffset; - } - if (e.NewValue is FileTabViewModel newViewModel) { - ViewModel = newViewModel; - ContextMenu!.DataContext = newViewModel; - if (oldViewModel != null) { - newViewModel.FileView.StageChangesFromOther(oldViewModel.FileView); - } else { - newViewModel.FileView.StageAllChanges(); - } - if (scrollViewer != null) { - scrollViewer.ScrollToHorizontalOffset(newViewModel.ScrollViewX); - scrollViewer.ScrollToVerticalOffset(newViewModel.ScrollViewY); - } - } - } - - private static void OnViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + private static void OnFileViewChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var fileGrid = (FileListView)d; - if (e.NewValue is FileView fileView) { - fileView.PropertyChanged += fileGrid.OnFileViewPropertyChanged; - fileView.CommitChange(); - } + var fileView = (FileView)e.NewValue; + fileView.PropertyChanged += fileGrid.OnFileViewPropertyChanged; } private void OnFileViewPropertyChanged(object? sender, PropertyChangedEventArgs e) { - var fileView = FileView; - if (fileView == null) { - return; - } + var fileView = FileView!; switch (e.PropertyName) { case nameof(fileView.FileViewType): if (fileView.FileViewType == FileViewType.Details) { @@ -704,8 +663,9 @@ protected override void OnPreviewMouseUp(MouseButtonEventArgs e) { openedContextMenu.IsOpen = true; } else { UnselectAll(); - openedContextMenu = ContextMenu; - openedContextMenu!.IsOpen = true; + openedContextMenu = ContextMenu!; + openedContextMenu.DataContext = ViewModel; + openedContextMenu.IsOpen = true; } break; } @@ -731,7 +691,7 @@ protected override void OnSelectionChanged(SelectionChangedEventArgs e) { return; } - viewModel.ChangeSelection(e); + ViewModel.ChangeSelection(e); } protected override void OnPreviewMouseWheel(MouseWheelEventArgs e) { @@ -999,11 +959,11 @@ public ItemClickEventArgs(RoutedEvent e, FileListViewItem item) { #region MenuItem点击事件,用Binding的话太浪费资源了 private void Refresh_OnClick(object sender, RoutedEventArgs e) { - viewModel.Refresh(); + ViewModel.Refresh(); } private void NewFolder_OnClick(object sender, RoutedEventArgs e) { - viewModel.CreateCommand.Execute(CreateFolderItem.Singleton); + ViewModel.CreateCommand.Execute(CreateFolderItem.Singleton); } private void FormatDiskDrive_OnClick(object sender, RoutedEventArgs e) { diff --git a/ExplorerEx/View/Controls/FileViewGrid.xaml b/ExplorerEx/View/Controls/FileViewGrid.xaml index 2289f72..086c10a 100644 --- a/ExplorerEx/View/Controls/FileViewGrid.xaml +++ b/ExplorerEx/View/Controls/FileViewGrid.xaml @@ -10,6 +10,7 @@ xmlns:vm="clr-namespace:ExplorerEx.ViewModel" xmlns:e="clr-namespace:ExplorerEx.Model.Enums" xmlns:c="clr-namespace:ExplorerEx.Converter" + xmlns:ex="clr-namespace:ExplorerEx" mc:Ignorable="d" d:DataContext="{d:DesignInstance vm:FileTabViewModel}"> @@ -20,9 +21,9 @@ - + @@ -35,7 +36,7 @@ - + - - - + @@ -70,10 +68,4 @@ - - - - - - diff --git a/ExplorerEx/View/Controls/TabControl/FileTabControl.xaml.cs b/ExplorerEx/View/Controls/TabControl/FileTabControl.xaml.cs index 055d6be..85a6e00 100644 --- a/ExplorerEx/View/Controls/TabControl/FileTabControl.xaml.cs +++ b/ExplorerEx/View/Controls/TabControl/FileTabControl.xaml.cs @@ -1,6 +1,4 @@ -#nullable enable - -using System; +using System; using ExplorerEx.Utils; using ExplorerEx.ViewModel; using HandyControl.Data; @@ -125,8 +123,31 @@ public double TabItemHeight { /// public ObservableCollection TabItems { get; } = new(); + private static readonly Dictionary CachedViews = new(); + public new static readonly DependencyProperty SelectedIndexProperty = DependencyProperty.Register( - nameof(SelectedIndex), typeof(int), typeof(FileTabControl), new PropertyMetadata(default(int))); + nameof(SelectedIndex), typeof(int), typeof(FileTabControl), new PropertyMetadata(0, SelectedIndexProperty_OnChanged)); + + /// + /// 在这里关闭虚拟化 + /// + /// + /// + /// + private static void SelectedIndexProperty_OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + var tabControl = (FileTabControl)d; + var index = (int)e.NewValue; + if (index == -1) { + tabControl.ContentPanel.Child = null; + return; + } + var tabViewModel = tabControl.TabItems[index]; + if (!CachedViews.TryGetValue(tabViewModel, out var fileViewGrid)) { + CachedViews.Add(tabViewModel, new FileViewGrid(tabViewModel, tabControl.ContentPanel)); + } else { + tabControl.ContentPanel.Child = fileViewGrid; + } + } public new int SelectedIndex { get => (int)GetValue(SelectedIndexProperty); @@ -156,14 +177,6 @@ public FileTabViewModel SelectedTab { } } - public static readonly DependencyProperty IsFileUtilsVisibleProperty = DependencyProperty.Register( - nameof(IsFileUtilsVisible), typeof(bool), typeof(FileTabControl), new PropertyMetadata(default(bool))); - - public bool IsFileUtilsVisible { - get => (bool)GetValue(IsFileUtilsVisibleProperty); - set => SetValue(IsFileUtilsVisibleProperty, value); - } - public MainWindow MainWindow { get; } public SplitGrid OwnerSplitGrid { get; set; } @@ -220,6 +233,7 @@ public async Task StartUpLoad(params string[]? startupPaths) { /// public void Close() { foreach (var tab in TabItems) { + CachedViews.Remove(tab); tab.Dispose(); } TabItems.Clear(); @@ -237,6 +251,7 @@ public async void CloseTab(FileTabViewModel tab) { return; } if (await HandleTabClosing(tab)) { + CachedViews.Remove(tab); TabItems.RemoveAt(index); } } @@ -422,13 +437,6 @@ internal async Task HandleTabClosing(FileTabViewModel tab) { return true; } - protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) { - base.OnRenderSizeChanged(sizeInfo); - if (sizeInfo.WidthChanged) { - IsFileUtilsVisible = sizeInfo.NewSize.Width > 640d; - } - } - private static bool CanDragDrop(DragEventArgs e, FileTabViewModel vm) { if (vm.PathType == PathType.Home) { e.Effects = DragDropEffects.None; @@ -547,6 +555,7 @@ public override void OnApplyTemplate() { headerBorder = (Border)GetTemplateChild(HeaderBorderKey)!; NewTabButton = (Button)GetTemplateChild(NewTabButtonKey)!; ContentPanel = (Border)GetTemplateChild(ContentPanelKey)!; + SelectedIndexProperty_OnChanged(this, new DependencyPropertyChangedEventArgs(SelectedIndexProperty, 0, 0)); } private void TabBorder_OnMouseEnter(object sender, MouseEventArgs e) { diff --git a/ExplorerEx/View/Controls/TabControl/FileTabItem.cs b/ExplorerEx/View/Controls/TabControl/FileTabItem.cs index 195c2a0..4e20b0a 100644 --- a/ExplorerEx/View/Controls/TabControl/FileTabItem.cs +++ b/ExplorerEx/View/Controls/TabControl/FileTabItem.cs @@ -128,7 +128,7 @@ public event EventHandler TabCommand { private FileTabPanel? fileTabPanel; - private Grid templateRoot; + private Grid templateRoot = null!; private static readonly DoubleAnimation TabShowAnimation = new(1d, new Duration(TimeSpan.FromMilliseconds(300))) { EasingFunction = new SineEase { @@ -138,7 +138,7 @@ public event EventHandler TabCommand { public FileTabItem() { CommandBindings.Add(new CommandBinding(ControlCommands.Close, (_, _) => Close())); - CommandBindings.Add(new CommandBinding(ControlCommands.CloseOther, (_, _) => TabControlParent.CloseOtherItems(this))); + CommandBindings.Add(new CommandBinding(ControlCommands.CloseOther, (_, _) => TabControlParent?.CloseOtherItems(this))); Loaded += (s, _) => { var tab = (FileTabItem)s; if (tab.ViewModel.playTabAnimation) { @@ -205,7 +205,7 @@ internal int CurrentIndex { /// /// private void UpdateItemOffsetX(int oldIndex) { - if (!isDragging || CurrentIndex >= FileTabPanel.ItemDict.Count) { + if (!isDragging || FileTabPanel == null || CurrentIndex >= FileTabPanel.ItemDict.Count) { return; } @@ -247,11 +247,10 @@ internal async void Close() { return; } - FileTabPanel.SetValue(FileTabPanel.FluidMoveDurationPropertyKey, new Duration(TimeSpan.FromMilliseconds(200))); + FileTabPanel?.SetValue(FileTabPanel.FluidMoveDurationPropertyKey, new Duration(TimeSpan.FromMilliseconds(200))); parent.IsInternalAction = true; - var list = parent.GetActualList(); - list?.Remove(item); + parent.GetActualList().Remove(item); } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { @@ -260,19 +259,21 @@ protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { if (VisualTreeHelper.HitTest(this, e.GetPosition(this)) == null) { return; } + // 所有TabItem放在这个里边 - if (TabControlParent == null) { + var parent = TabControlParent; + if (parent == null) { return; } - var parent = TabControlParent.TabBorder; + var parentBorder = parent.TabBorder; if (!isItemDragging && !isDragging) { - FileTabPanel.SetValue(FileTabPanel.FluidMoveDurationPropertyKey, new Duration(TimeSpan.FromSeconds(0))); + FileTabPanel?.SetValue(FileTabPanel.FluidMoveDurationPropertyKey, new Duration(TimeSpan.FromSeconds(0))); mouseDownOffsetX = RenderTransform.Value.OffsetX; - var mx = TranslatePoint(new Point(), parent).X; - mouseDownPoint = e.GetPosition(parent); + var mx = TranslatePoint(new Point(), parentBorder).X; + mouseDownPoint = e.GetPosition(parentBorder); mouseDownTabPoint = e.GetPosition(this); - StartDrag(parent, mouseDownPoint, CalLocationIndex(mx)); + StartDrag(parentBorder, mouseDownPoint, CalLocationIndex(mx)); } } @@ -445,6 +446,9 @@ protected override void OnMouseDown(MouseButtonEventArgs e) { /// internal void CreateAnimation(double offsetX, double resultX, int index = -1) { var parent = TabControlParent; + if (parent == null) { + return; + } void AnimationCompleted() { RenderTransform = new TranslateTransform(resultX, 0); @@ -453,10 +457,6 @@ void AnimationCompleted() { } var list = parent.GetActualList(); - if (list == null) { - return; - } - var item = parent.ItemContainerGenerator.ItemFromContainer(this); if (item == null) { return; @@ -471,8 +471,11 @@ void AnimationCompleted() { list.Insert(index, item); } - fileTabPanel.SetValue(FileTabPanel.FluidMoveDurationPropertyKey, new Duration(TimeSpan.FromMilliseconds(0))); - FileTabPanel.Measure(new Size(FileTabPanel.DesiredSize.Width, ActualHeight)); + var fileTabPanel = FileTabPanel; + if (fileTabPanel != null) { + fileTabPanel.SetValue(FileTabPanel.FluidMoveDurationPropertyKey, new Duration(TimeSpan.FromMilliseconds(0))); + fileTabPanel.Measure(new Size(fileTabPanel.DesiredSize.Width, ActualHeight)); + } Focus(); IsSelected = true; @@ -504,7 +507,12 @@ private int CalLocationIndex(double left) { return CurrentIndex; } - var maxIndex = TabControlParent.Items.Count - 1; + var parent = TabControlParent; + if (parent == null) { + return 0; + } + + var maxIndex = parent.Items.Count - 1; var div = (int)(left / ItemWidth); var rest = left % ItemWidth; var result = rest / ItemWidth > .5 ? div + 1 : div; diff --git a/ExplorerEx/View/Controls/TabControl/FileTabPanel.cs b/ExplorerEx/View/Controls/TabControl/FileTabPanel.cs index 4dcb818..e168338 100644 --- a/ExplorerEx/View/Controls/TabControl/FileTabPanel.cs +++ b/ExplorerEx/View/Controls/TabControl/FileTabPanel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Windows; using System.Windows.Controls; +using System.Windows.Input; using System.Windows.Media; namespace ExplorerEx.View.Controls; @@ -21,13 +22,13 @@ public class FileTabPanel : Panel { /// 标签宽度 /// public static readonly DependencyProperty TabItemWidthProperty = DependencyProperty.Register( - "TabItemWidth", typeof(double), typeof(FileTabPanel), new PropertyMetadata(200.0)); + nameof(TabItemWidth), typeof(double), typeof(FileTabPanel), new PropertyMetadata(200.0)); /// /// 标签高度 /// public static readonly DependencyProperty TabItemHeightProperty = DependencyProperty.Register( - "TabItemHeight", typeof(double), typeof(FileTabPanel), new PropertyMetadata(30.0)); + nameof(TabItemHeight), typeof(double), typeof(FileTabPanel), new PropertyMetadata(30.0)); /// /// 是否已经加载 @@ -78,6 +79,13 @@ public double TabItemHeight { set => SetValue(TabItemHeightProperty, value); } + /// + /// 存储之前的,当鼠标在上面的时候,就不自动把标签页宽度调大,方便连续关闭 + /// + private double prevActualItemWidth = double.PositiveInfinity; + + private bool isWaitingForMouseLeave; + protected override Size MeasureOverride(Size constraint) { if (TemplatedParent is not FileTabControl tabControl) { return oldSize; @@ -101,6 +109,19 @@ protected override Size MeasureOverride(Size constraint) { if (containerWidth > 0 && itemWidth * count > containerWidth) { itemWidth = containerWidth / count; } + var actualItemWidth = Math.Max(itemWidth - 1, 1); + if (actualItemWidth > prevActualItemWidth) { + // 标签页变大了,说明关闭了,这个时候就看鼠标指针是不是在上面悬浮着 + if (IsMouseOver) { + actualItemWidth = prevActualItemWidth; // 就先不变大,保留 + itemWidth = actualItemWidth + 1; + isWaitingForMouseLeave = true; + } else { + prevActualItemWidth = actualItemWidth; + } + } else { + prevActualItemWidth = actualItemWidth; + } for (var index = 0; index < count; index++) { if (InternalChildren[index] is FileTabItem tabItem) { @@ -113,11 +134,11 @@ protected override Size MeasureOverride(Size constraint) { }; newButtonLeft = Math.Max(newButtonLeft, rect.X + itemWidth); tabItem.Arrange(rect); - tabItem.ItemWidth = Math.Max(itemWidth - tabItem.BorderThickness.Left, 1); + tabItem.ItemWidth = actualItemWidth; tabItem.CurrentIndex = index; tabItem.TargetOffsetX = 0; ItemDict[index] = tabItem; - size.Width += tabItem.ItemWidth; + size.Width += actualItemWidth; } } tabControl.NewTabButton.Margin = new Thickness(newButtonLeft + 12, 5, 0, 3); @@ -126,4 +147,13 @@ protected override Size MeasureOverride(Size constraint) { oldSize = size; return oldSize; } + + protected override void OnMouseLeave(MouseEventArgs e) { + base.OnMouseLeave(e); + if (isWaitingForMouseLeave) { + isWaitingForMouseLeave = false; + prevActualItemWidth = double.PositiveInfinity; + Measure(new Size(DesiredSize.Width, ActualHeight)); + } + } } \ No newline at end of file diff --git a/ExplorerEx/View/Controls/TabControl/FluentTabControl.cs b/ExplorerEx/View/Controls/TabControl/FluentTabControl.cs index be9d2c8..32e9bb2 100644 --- a/ExplorerEx/View/Controls/TabControl/FluentTabControl.cs +++ b/ExplorerEx/View/Controls/TabControl/FluentTabControl.cs @@ -12,7 +12,7 @@ namespace ExplorerEx.View.Controls; /// public class FluentTabControl : TabControl { public static readonly DependencyProperty CanDeselectProperty = DependencyProperty.Register( - "CanDeselect", typeof(bool), typeof(FluentTabControl), new PropertyMetadata(default(bool))); + nameof(CanDeselect), typeof(bool), typeof(FluentTabControl), new PropertyMetadata(default(bool))); /// /// 是否可以再次点击TabItem取消选择 @@ -25,10 +25,10 @@ public bool CanDeselect { public SimpleCommand TabItemPreviewMouseDownCommand { get; } public SimpleCommand TabItemPreviewMouseUpCommand { get; } - private TabItem mouseDownFileTabItem; + private TabItem? mouseDownFileTabItem; private Point mouseDownPoint; - private Border contentPanel; - private FluentBorder fluentBorder; + private Border contentPanel = null!; + private FluentBorder fluentBorder = null!; private readonly Storyboard storyboard; private int? targetIndex; private static readonly CubicEase CubicEase = new() { EasingMode = EasingMode.EaseInOut }; @@ -43,21 +43,24 @@ public FluentTabControl() { public override void OnApplyTemplate() { base.OnApplyTemplate(); - contentPanel = (Border)GetTemplateChild("ContentPanel"); - fluentBorder = (FluentBorder)GetTemplateChild("FluentBorder"); + contentPanel = (Border)GetTemplateChild("ContentPanel")!; + fluentBorder = (FluentBorder)GetTemplateChild("FluentBorder")!; Storyboard.SetTarget(storyboard, fluentBorder); + if (SelectedIndex == -1) { + contentPanel.Visibility = Visibility.Collapsed; + } } - private void OnTabItemPreviewMouseDown(object args) { - var e = (MouseButtonEventArgs)args; - mouseDownFileTabItem = (TabItem)ContainerFromElement((DependencyObject)e.OriginalSource); + private void OnTabItemPreviewMouseDown(object? args) { + var e = (MouseButtonEventArgs)args!; + mouseDownFileTabItem = (TabItem)ContainerFromElement((DependencyObject)e.OriginalSource)!; mouseDownPoint = e.GetPosition(this); e.Handled = true; } - private void OnTabItemPreviewMouseUp(object args) { - var e = (MouseButtonEventArgs)args; - var tabItem = (TabItem)ContainerFromElement((DependencyObject)e.OriginalSource); + private void OnTabItemPreviewMouseUp(object? args) { + var e = (MouseButtonEventArgs)args!; + var tabItem = (TabItem?)ContainerFromElement((DependencyObject)e.OriginalSource); if (tabItem != null && tabItem == mouseDownFileTabItem) { var point = e.GetPosition(this); if (Math.Abs(point.X - mouseDownPoint.X) < SystemParameters.MinimumHorizontalDragDistance && Math.Abs(point.Y - mouseDownPoint.Y) < SystemParameters.MinimumVerticalDragDistance) { @@ -74,6 +77,11 @@ private void OnTabItemPreviewMouseUp(object args) { protected override void OnSelectionChanged(SelectionChangedEventArgs e) { base.OnSelectionChanged(e); + if (SelectedIndex == -1) { + contentPanel.Visibility = Visibility.Collapsed; + } else { + contentPanel.Visibility = Visibility.Visible; + } if (targetIndex == null) { targetIndex = SelectedIndex; Animate(); diff --git a/ExplorerEx/View/MainWindow.xaml b/ExplorerEx/View/MainWindow.xaml index c777237..241ad75 100644 --- a/ExplorerEx/View/MainWindow.xaml +++ b/ExplorerEx/View/MainWindow.xaml @@ -207,7 +207,7 @@ + WindowChrome.IsHitTestVisibleInChrome="True" CanDeselect="True" SelectionChanged="OnSidebarSelectionChanged"> - + @@ -263,7 +263,7 @@ - + @@ -314,7 +314,7 @@ ItemsSource="{Binding Path=Children, Source={x:Static m:FolderOnlyItem.Home}}"> - + diff --git a/ExplorerEx/View/MainWindow.xaml.cs b/ExplorerEx/View/MainWindow.xaml.cs index 8abfca8..8e1d157 100644 --- a/ExplorerEx/View/MainWindow.xaml.cs +++ b/ExplorerEx/View/MainWindow.xaml.cs @@ -26,7 +26,6 @@ using System.Diagnostics; using System.Windows.Data; using System.Windows.Threading; -using ExplorerEx.Database.Interface; using hc = HandyControl.Controls; using HandyControl.Data; using HandyControl.Tools.Interop; @@ -459,7 +458,7 @@ public void UnRegisterEverythingQuery(uint id) { } #region 收藏夹 - public readonly DependencyProperty IsAddToBookmarkShowProperty = DependencyProperty.Register( + public static readonly DependencyProperty IsAddToBookmarkShowProperty = DependencyProperty.Register( nameof(IsAddToBookmarkShow), typeof(bool), typeof(MainWindow), new PropertyMetadata(false, OnIsAddToBookmarkShowChanged)); public bool IsAddToBookmarkShow { @@ -535,7 +534,7 @@ private static async void OnIsAddToBookmarkShowChanged(DependencyObject d, Depen var categoryItem = dbCtx.FirstOrDefault((BookmarkCategory bc) => bc.Name == category); if (categoryItem == null) { categoryItem = new BookmarkCategory(category); - await dbCtx.AddAsync(categoryItem); + dbCtx.Add(categoryItem); } var fullPath = Path.GetFullPath(bookmarkItem); var dbBookmark = dbCtx.FirstOrDefault(b => b.FullPath == fullPath); @@ -546,7 +545,7 @@ private static async void OnIsAddToBookmarkShowChanged(DependencyObject d, Depen await dbCtx.SaveAsync(); } else { var item = new BookmarkItem(bookmarkItem, window.BookmarkName, categoryItem); - await dbCtx.AddAsync(item); + dbCtx.Add(item); await dbCtx.SaveAsync(); item.LoadIcon(FileListViewItem.LoadDetailsOptions.Default); } @@ -760,15 +759,16 @@ private void SettingsButton_OnClick(object sender, RoutedEventArgs e) { #region Overrides protected override void OnClosing(CancelEventArgs e) { + base.OnClosing(e); if (SplitGrid.FileTabControl.TabItems.Count > 1 || SplitGrid.AnySplitScreen) { - if (!ContentDialog.ShowWithDefault("CloseMultiTabs", "#YouHaveOpenedMoreThanOneTab".L())) { + if (!ContentDialog.ShowWithDefault(Settings.CommonSettings.DontAskWhenClosingMultiTabs, "#YouHaveOpenedMoreThanOneTab".L())) { e.Cancel = true; } } - base.OnClosing(e); } protected override void OnClosed(EventArgs e) { + base.OnClosed(e); Settings.ThemeChanged -= ChangeTheme; All.Remove(this); @@ -777,7 +777,6 @@ protected override void OnClosed(EventArgs e) { FrequentTimer.Stop(); RecycleBinItem.UnregisterAllWatchers(); } - base.OnClosed(e); } protected override void OnStateChanged(EventArgs e) { diff --git a/ExplorerEx/View/NotifyIconWindow.xaml.cs b/ExplorerEx/View/NotifyIconWindow.xaml.cs index d0bba73..26d7328 100644 --- a/ExplorerEx/View/NotifyIconWindow.xaml.cs +++ b/ExplorerEx/View/NotifyIconWindow.xaml.cs @@ -5,6 +5,8 @@ using ExplorerEx.Win32; using System.Runtime.InteropServices; using System.Windows.Media; +using System.Globalization; +using System.Threading; namespace ExplorerEx.View; @@ -13,6 +15,8 @@ namespace ExplorerEx.View; /// public partial class NotifyIconWindow { public NotifyIconWindow() { + Thread.CurrentThread.CurrentUICulture = new CultureInfo(Settings.Current[Settings.CommonSettings.Language].GetInt32()); + DataContext = this; InitializeComponent(); diff --git a/ExplorerEx/ViewModel/FileTabViewModel.cs b/ExplorerEx/ViewModel/FileTabViewModel.cs index 20df973..2bb5cb2 100644 --- a/ExplorerEx/ViewModel/FileTabViewModel.cs +++ b/ExplorerEx/ViewModel/FileTabViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; @@ -162,13 +163,6 @@ private string? ParentFolderName { public bool CanDeleteOrCut => IsItemSelected && SelectedItems.All(i => i is FileSystemItem); - /// - /// 视窗的滚动位置 - /// - public double ScrollViewX { get; set; } - - public double ScrollViewY { get; set; } - /// /// 文件项的Command /// @@ -479,7 +473,7 @@ private Task LoadThumbnailsAsync() { /// /// 为null表示新建,不为null就是修改,要确保是从Db里拿到的对象否则修改没有效果 /// - private async Task SaveViewToDbAsync(FileView? fileView) { + private Task SaveViewToDbAsync(FileView? fileView) { var fullPath = FullPath; var dbCtx = App.FileViewDbContext; fileView ??= dbCtx.FirstOrDefault(v => v.FullPath == fullPath); @@ -493,7 +487,7 @@ private async Task SaveViewToDbAsync(FileView? fileView) { ItemSize = ItemSize, DetailLists = DetailLists }; - await dbCtx.AddAsync(fileView); + dbCtx.Add(fileView); } else { Debug.Assert(fileView.FullPath == fullPath); fileView.SortBy = SortBy; @@ -502,8 +496,9 @@ private async Task SaveViewToDbAsync(FileView? fileView) { fileView.FileViewType = FileViewType; fileView.ItemSize = ItemSize; fileView.DetailLists = DetailLists; + dbCtx.Update(fileView); } - await dbCtx.SaveAsync(); + return dbCtx.SaveAsync(); } private void OnClipboardChanged() { @@ -585,14 +580,9 @@ public async Task LoadDirectoryAsync(string? path, bool recordHistory = tr } FileItemCommand.Folder = Folder; + Folder.LoadIcon(loadDetailsOptions); - try { - _ = Task.Run(() => Folder.LoadIcon(loadDetailsOptions)); - } catch { - // 加载图标出错,忽略 - } - - if (Folder.GetType() == typeof(FolderItem) || Folder is DiskDriveItem) { + if (!Folder.IsVirtual) { try { watcher.Path = Folder.FullPath; watcher.Enabled = true; @@ -741,8 +731,24 @@ public async Task LoadDirectoryAsync(string? path, bool recordHistory = tr return !token.IsCancellationRequested; } + /// + /// 遇到错误需要返回 + /// + /// private Task ErrorGoBack() { if (CanGoBack) { + var originalPath = HistoryList[--nextHistoryIndex].FullPath; + while (nextHistoryIndex > 1) { + var currentPath = HistoryList[nextHistoryIndex - 1].FullPath; + var prevPath = HistoryList[nextHistoryIndex].FullPath; + if (currentPath.Length > prevPath.Length && currentPath.StartsWith(prevPath)) { // 如果这个路径包含了之前的路径,那还是不行,继续回退 + nextHistoryIndex--; + } else if (currentPath == originalPath) { // 如果相等,还是不得行 + nextHistoryIndex--; + } else { + break; + } + } return LoadDirectoryAsync(HistoryList[nextHistoryIndex - 1].FullPath, false, FullPath); } return LoadDirectoryAsync(null, false, FullPath); @@ -946,12 +952,20 @@ private async Task OnEverythingQueryReplied(uint id, EverythingInterop.QueryRepl } private void Watcher_OnError(Exception e) { - if (!Directory.Exists(FullPath)) { // 当前目录不复存在力,被删除力 + if (e is Win32Exception { NativeErrorCode: 5 }) { // 当前目录不复存在力,被删除力 string? parentPath; - do { + while (true) { parentPath = Path.GetDirectoryName(FullPath); - } while (parentPath != null && !Directory.Exists(parentPath)); - _ = LoadDirectoryAsync(parentPath); + if (parentPath == null) { + break; + } + var di = new DirectoryInfo(parentPath); + di.Refresh(); + if (di.Exists) { + break; + } + } + _ = LoadDirectoryAsync(parentPath, false); } else { Logger.Error(e.Message); } diff --git a/External/HandyControl/Controls/Other/TransitioningContentControl.cs b/External/HandyControl/Controls/Other/TransitioningContentControl.cs index 979178c..99de941 100644 --- a/External/HandyControl/Controls/Other/TransitioningContentControl.cs +++ b/External/HandyControl/Controls/Other/TransitioningContentControl.cs @@ -5,103 +5,96 @@ using HandyControl.Data; using HandyControl.Tools; -namespace HandyControl.Controls -{ - public class TransitioningContentControl : ContentControl - { - private FrameworkElement _contentPresenter; - - private static Storyboard StoryboardBuildInDefault; - - private Storyboard _storyboardBuildIn; - - public TransitioningContentControl() - { - Loaded += TransitioningContentControl_Loaded; - Unloaded += TransitioningContentControl_Unloaded; - } - - public static readonly DependencyProperty TransitionModeProperty = DependencyProperty.Register( - "TransitionMode", typeof(TransitionMode), typeof(TransitioningContentControl), new PropertyMetadata(default(TransitionMode), OnTransitionModeChanged)); - - private static void OnTransitionModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var ctl = (TransitioningContentControl) d; - ctl.OnTransitionModeChanged((TransitionMode) e.NewValue); - } - - private void OnTransitionModeChanged(TransitionMode newValue) - { - _storyboardBuildIn = ResourceHelper.GetResourceInternal($"{newValue}Transition"); - StartTransition(); - } - - public TransitionMode TransitionMode - { - get => (TransitionMode) GetValue(TransitionModeProperty); - set => SetValue(TransitionModeProperty, value); - } - - public static readonly DependencyProperty TransitionStoryboardProperty = DependencyProperty.Register( - "TransitionStoryboard", typeof(Storyboard), typeof(TransitioningContentControl), new PropertyMetadata(default(Storyboard))); - - public Storyboard TransitionStoryboard - { - get => (Storyboard) GetValue(TransitionStoryboardProperty); - set => SetValue(TransitionStoryboardProperty, value); - } - - private void TransitioningContentControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) => StartTransition(); - - private void TransitioningContentControl_Loaded(object sender, RoutedEventArgs e) - { - StartTransition(); - IsVisibleChanged += TransitioningContentControl_IsVisibleChanged; - } - - private void TransitioningContentControl_Unloaded(object sender, RoutedEventArgs e) - { - IsVisibleChanged -= TransitioningContentControl_IsVisibleChanged; - } - - private void StartTransition() - { - if (!IsArrangeValid || _contentPresenter == null) return; - - if (TransitionStoryboard != null) - { - TransitionStoryboard.Begin(_contentPresenter); - } - else if (_storyboardBuildIn != null) - { - _storyboardBuildIn?.Begin(_contentPresenter); - } - else - { - StoryboardBuildInDefault ??= ResourceHelper.GetResourceInternal($"{default(TransitionMode)}Transition"); - StoryboardBuildInDefault?.Begin(_contentPresenter); - } - } - - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - _contentPresenter = VisualTreeHelper.GetChild(this, 0) as FrameworkElement; - if (_contentPresenter != null) - { - _contentPresenter.RenderTransformOrigin = new Point(0.5, 0.5); - _contentPresenter.RenderTransform = new TransformGroup - { - Children = - { - new ScaleTransform(), - new SkewTransform(), - new RotateTransform(), - new TranslateTransform() - } - }; - } - } - } -} +namespace HandyControl.Controls; + +public class TransitioningContentControl : ContentControl { + private FrameworkElement contentPresenter; + + private static Storyboard storyboardBuildInDefault; + + private Storyboard storyboardBuildIn; + + public TransitioningContentControl() { + Loaded += TransitioningContentControl_Loaded; + Unloaded += TransitioningContentControl_Unloaded; + } + + public static readonly DependencyProperty TransitionModeProperty = DependencyProperty.Register( + nameof(TransitionMode), typeof(TransitionMode), typeof(TransitioningContentControl), new PropertyMetadata(default(TransitionMode), OnTransitionModeChanged)); + + private static void OnTransitionModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + var ctl = (TransitioningContentControl)d; + ctl.OnTransitionModeChanged((TransitionMode)e.NewValue); + } + + private void OnTransitionModeChanged(TransitionMode newValue) { + storyboardBuildIn = ResourceHelper.GetResourceInternal($"{newValue}Transition"); + StartTransition(); + } + + public TransitionMode TransitionMode { + get => (TransitionMode)GetValue(TransitionModeProperty); + set => SetValue(TransitionModeProperty, value); + } + + public static readonly DependencyProperty TransitionStoryboardProperty = DependencyProperty.Register( + nameof(TransitionStoryboard), typeof(Storyboard), typeof(TransitioningContentControl), new PropertyMetadata(default(Storyboard))); + + public Storyboard TransitionStoryboard { + get => (Storyboard)GetValue(TransitionStoryboardProperty); + set => SetValue(TransitionStoryboardProperty, value); + } + + private void TransitioningContentControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) => StartTransition(); + + private void TransitioningContentControl_Loaded(object sender, RoutedEventArgs e) { + IsVisibleChanged += TransitioningContentControl_IsVisibleChanged; + } + + private void TransitioningContentControl_Unloaded(object sender, RoutedEventArgs e) { + IsVisibleChanged -= TransitioningContentControl_IsVisibleChanged; + } + + private void StartTransition() { + if (!IsArrangeValid || contentPresenter == null) { + return; + } + + if (TransitionStoryboard != null) { + TransitionStoryboard.Begin(contentPresenter); + } else if (storyboardBuildIn != null) { + storyboardBuildIn?.Begin(contentPresenter); + } else { + storyboardBuildInDefault ??= ResourceHelper.GetResourceInternal($"{default(TransitionMode)}Transition"); + storyboardBuildInDefault?.Begin(contentPresenter); + } + } + + public override void OnApplyTemplate() { + base.OnApplyTemplate(); + + contentPresenter = VisualTreeHelper.GetChild(this, 0) as FrameworkElement; + if (contentPresenter != null) { + contentPresenter.RenderTransformOrigin = new Point(0.5, 0.5); + contentPresenter.RenderTransform = new TransformGroup { + Children = { + new ScaleTransform(), + new SkewTransform(), + new RotateTransform(), + new TranslateTransform() + } + }; + } + } + + private bool rendered; + + protected override void OnRender(DrawingContext drawingContext) { + base.OnRender(drawingContext); + if (rendered) { + return; + } + rendered = true; + StartTransition(); + } +} \ No newline at end of file diff --git a/External/HandyControl/Themes/Basic/Geometries.xaml b/External/HandyControl/Themes/Basic/Geometries.xaml index 05a2504..4fac213 100644 --- a/External/HandyControl/Themes/Basic/Geometries.xaml +++ b/External/HandyControl/Themes/Basic/Geometries.xaml @@ -58,128 +58,4 @@ M14,0 C15.104569,0 16,0.89543051 16,2 16,3.1045694 15.104569,4 14,4 12.895431,4 12,3.1045694 12,2 12,0.89543051 12.895431,0 14,0 z M8,0 C9.1045694,0 10,0.89543051 10,2 10,3.1045694 9.1045694,4 8,4 6.8954306,4 6,3.1045694 6,2 6,0.89543051 6.8954306,0 8,0 z M2,0 C3.1045694,0 4,0.89543051 4,2 4,3.1045694 3.1045694,4 2,4 0.89543056,4 0,3.1045694 0,2 0,0.89543051 0.89543056,0 2,0 z M798.165333 97.834667a42.624 42.624 0 0 0-60.330666 0l-140.629334 140.629333-55.381333-55.296-60.330667 60.330667 55.381334 55.296-353.706667 353.706666a42.709333 42.709333 0 0 0-11.221333 19.84l-42.666667 170.666667a42.538667 42.538667 0 0 0 51.712 51.712l170.666667-42.666667c7.509333-1.877333 14.378667-5.76 19.84-11.221333l353.792-353.792 55.210666 55.125333 60.330667-60.330666-55.210667-55.125334 140.544-140.544a42.624 42.624 0 0 0 0-60.330666l-128-128zM319.488 772.138667l-90.197333 22.570666 22.570666-90.197333 345.386667-345.386667 67.669333 67.584-345.429333 345.429334z M861.866667 164.266667c-87.466667-87.466667-230.4-89.6-320-2.133334l-68.266667 68.266667c-12.8 12.8-12.8 32 0 44.8s32 12.8 44.8 0l68.266667-68.266667c64-61.866667 166.4-61.866667 230.4 2.133334 64 64 64 168.533333 2.133333 232.533333l-117.333333 119.466667c-34.133333 34.133333-81.066667 51.2-128 49.066666-46.933333-4.266667-91.733333-27.733333-119.466667-66.133333-10.666667-14.933333-29.866667-17.066667-44.8-6.4-14.933333 10.666667-17.066667 29.866667-6.4 44.8 40.533333 53.333333 100.266667 87.466667 166.4 91.733333h17.066667c59.733333 0 119.466667-23.466667 162.133333-68.266666l117.333333-119.466667c83.2-89.6 83.2-234.666667-4.266666-322.133333zM505.6 750.933333l-66.133333 68.266667c-64 61.866667-166.4 61.866667-230.4-2.133333-64-64-64-168.533333-2.133334-232.533334l117.333334-119.466666c34.133333-34.133333 81.066667-51.2 128-49.066667 46.933333 4.266667 91.733333 27.733333 119.466666 66.133333 10.666667 14.933333 29.866667 17.066667 44.8 6.4 14.933333-10.666667 17.066667-29.866667 6.4-44.8-40.533333-53.333333-100.266667-87.466667-166.4-91.733333-66.133333-4.266667-130.133333 19.2-177.066666 66.133333l-117.333334 119.466667c-85.333333 89.6-85.333333 234.666667 2.133334 322.133333 44.8 44.8 102.4 66.133333 162.133333 66.133334 57.6 0 115.2-21.333333 160-64l66.133333-68.266667c12.8-12.8 12.8-32 0-44.8-14.933333-10.666667-34.133333-10.666667-46.933333 2.133333z - - M532.5 513h-206V495h206A19.6 19.6 0 00552 475.5v-412A19.6 19.6 0 00532.5 44h-412A19.6 19.6 0 00101 63.5v412A19.6 19.6 0 00120.5 495h8.7v18h-8.7A37.6 37.6 0 0183 475.5v-412A37.6 37.6 0 01120.5 26h412A37.6 37.6 0 01570 63.5v412A37.6 37.6 0 01532.5 513ZM426.3 462.2l-64.8-41C209.4 573.4 40 582 40 582c153-34.8 224.4-222.2 224.4-222.2l-64.8-41 216-90.5 10.6 234Z - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/External/HandyControl/Themes/Basic/Sizes.xaml b/External/HandyControl/Themes/Basic/Sizes.xaml index 3c395c0..ea482e4 100644 --- a/External/HandyControl/Themes/Basic/Sizes.xaml +++ b/External/HandyControl/Themes/Basic/Sizes.xaml @@ -2,10 +2,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib"> - 30 + 32 10,6 8,6 4 - 30 + 32 diff --git a/External/HandyControl/Themes/Styles/Base/MenuBaseStyle.xaml b/External/HandyControl/Themes/Styles/Base/MenuBaseStyle.xaml index 528e2d7..8bc73c0 100644 --- a/External/HandyControl/Themes/Styles/Base/MenuBaseStyle.xaml +++ b/External/HandyControl/Themes/Styles/Base/MenuBaseStyle.xaml @@ -170,7 +170,7 @@ - + diff --git a/External/HandyControl/Themes/Theme.xaml b/External/HandyControl/Themes/Theme.xaml index 47119e9..70e6a53 100644 --- a/External/HandyControl/Themes/Theme.xaml +++ b/External/HandyControl/Themes/Theme.xaml @@ -94,134 +94,16 @@ M14,0 C15.104569,0 16,0.89543051 16,2 16,3.1045694 15.104569,4 14,4 12.895431,4 12,3.1045694 12,2 12,0.89543051 12.895431,0 14,0 z M8,0 C9.1045694,0 10,0.89543051 10,2 10,3.1045694 9.1045694,4 8,4 6.8954306,4 6,3.1045694 6,2 6,0.89543051 6.8954306,0 8,0 z M2,0 C3.1045694,0 4,0.89543051 4,2 4,3.1045694 3.1045694,4 2,4 0.89543056,4 0,3.1045694 0,2 0,0.89543051 0.89543056,0 2,0 z M798.165333 97.834667a42.624 42.624 0 0 0-60.330666 0l-140.629334 140.629333-55.381333-55.296-60.330667 60.330667 55.381334 55.296-353.706667 353.706666a42.709333 42.709333 0 0 0-11.221333 19.84l-42.666667 170.666667a42.538667 42.538667 0 0 0 51.712 51.712l170.666667-42.666667c7.509333-1.877333 14.378667-5.76 19.84-11.221333l353.792-353.792 55.210666 55.125333 60.330667-60.330666-55.210667-55.125334 140.544-140.544a42.624 42.624 0 0 0 0-60.330666l-128-128zM319.488 772.138667l-90.197333 22.570666 22.570666-90.197333 345.386667-345.386667 67.669333 67.584-345.429333 345.429334z M861.866667 164.266667c-87.466667-87.466667-230.4-89.6-320-2.133334l-68.266667 68.266667c-12.8 12.8-12.8 32 0 44.8s32 12.8 44.8 0l68.266667-68.266667c64-61.866667 166.4-61.866667 230.4 2.133334 64 64 64 168.533333 2.133333 232.533333l-117.333333 119.466667c-34.133333 34.133333-81.066667 51.2-128 49.066666-46.933333-4.266667-91.733333-27.733333-119.466667-66.133333-10.666667-14.933333-29.866667-17.066667-44.8-6.4-14.933333 10.666667-17.066667 29.866667-6.4 44.8 40.533333 53.333333 100.266667 87.466667 166.4 91.733333h17.066667c59.733333 0 119.466667-23.466667 162.133333-68.266666l117.333333-119.466667c83.2-89.6 83.2-234.666667-4.266666-322.133333zM505.6 750.933333l-66.133333 68.266667c-64 61.866667-166.4 61.866667-230.4-2.133333-64-64-64-168.533333-2.133334-232.533334l117.333334-119.466666c34.133333-34.133333 81.066667-51.2 128-49.066667 46.933333 4.266667 91.733333 27.733333 119.466666 66.133333 10.666667 14.933333 29.866667 17.066667 44.8 6.4 14.933333-10.666667 17.066667-29.866667 6.4-44.8-40.533333-53.333333-100.266667-87.466667-166.4-91.733333-66.133333-4.266667-130.133333 19.2-177.066666 66.133333l-117.333334 119.466667c-85.333333 89.6-85.333333 234.666667 2.133334 322.133333 44.8 44.8 102.4 66.133333 162.133333 66.133334 57.6 0 115.2-21.333333 160-64l66.133333-68.266667c12.8-12.8 12.8-32 0-44.8-14.933333-10.666667-34.133333-10.666667-46.933333 2.133333z - M532.5 513h-206V495h206A19.6 19.6 0 00552 475.5v-412A19.6 19.6 0 00532.5 44h-412A19.6 19.6 0 00101 63.5v412A19.6 19.6 0 00120.5 495h8.7v18h-8.7A37.6 37.6 0 0183 475.5v-412A37.6 37.6 0 01120.5 26h412A37.6 37.6 0 01570 63.5v412A37.6 37.6 0 01532.5 513ZM426.3 462.2l-64.8-41C209.4 573.4 40 582 40 582c153-34.8 224.4-222.2 224.4-222.2l-64.8-41 216-90.5 10.6 234Z - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 30 + 32 10,6 8,6 4 - 30 + 32 @@ -6341,7 +6223,7 @@ - + diff --git a/External/HandyControl/Tools/Converter/TreeViewItemMarginConverter.cs b/External/HandyControl/Tools/Converter/TreeViewItemMarginConverter.cs index cc37970..6d930cb 100644 --- a/External/HandyControl/Tools/Converter/TreeViewItemMarginConverter.cs +++ b/External/HandyControl/Tools/Converter/TreeViewItemMarginConverter.cs @@ -4,25 +4,22 @@ using System.Windows.Data; using System.Windows.Media; -namespace HandyControl.Tools.Converter -{ - public class TreeViewItemMarginConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) - { - var left = 0.0; - UIElement element = value as TreeViewItem; - while (element != null && element.GetType() != typeof(TreeView)) - { - element = (UIElement) VisualTreeHelper.GetParent(element); - if (element is TreeViewItem) - left += 19.0; - } - return new Thickness(left, 0, 0, 0); - } - public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) - { - throw new NotSupportedException(); - } - } -} +namespace HandyControl.Tools.Converter; + +public class TreeViewItemMarginConverter : IValueConverter { + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { + var left = 0.0; + UIElement element = value as TreeViewItem; + while (element != null && element.GetType() != typeof(TreeView)) { + element = (UIElement)VisualTreeHelper.GetParent(element); + if (element is TreeViewItem) { + left += 10.0; + } + } + return new Thickness(left, 0, 0, 0); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { + throw new NotSupportedException(); + } +} \ No newline at end of file