From 093551b081ffc89d925fe1ca9dbea23c3550ccc6 Mon Sep 17 00:00:00 2001 From: "Dear.Va" <1328886154@qq.com> Date: Sat, 16 Jul 2022 13:26:56 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=87=E6=8D=A2=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E9=A1=B5=E6=97=B6=E9=80=89=E6=8B=A9=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E5=87=BA=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Build.cmd | 2 +- ExplorerEx/App.xaml | 1 - ExplorerEx/Command/FileItemCommand.cs | 5 +- ExplorerEx/ExplorerEx - Backup.csproj | 123 -------------- ExplorerEx/Model/CreateFileItem.cs | 41 +++-- ExplorerEx/Model/FileItem/FileListViewItem.cs | 2 +- ExplorerEx/Strings/Resources.Designer.cs | 9 ++ ExplorerEx/Strings/Resources.en.resx | 3 + ExplorerEx/Strings/Resources.resx | 3 + .../ConcurrentObservableCollection.cs | 151 +++++++++--------- .../DispatchedObservableCollection.cs | 70 ++++---- .../Internals/ObservableCollectionBase.cs | 13 +- .../Collections/Internals/PendingEvent`1.cs | 1 + ExplorerEx/Utils/FileUtils.cs | 42 +++-- ExplorerEx/View/Controls/CustomControls.xaml | 4 +- ExplorerEx/View/Controls/FileListView.xaml | 7 +- ExplorerEx/View/Controls/FileListView.xaml.cs | 80 ++++++++-- ExplorerEx/View/Controls/FileViewGrid.xaml | 12 +- ExplorerEx/View/Controls/SplitGrid.xaml.cs | 22 +-- .../View/Controls/TabControl/FileTabItem.cs | 3 +- ExplorerEx/View/MainWindow.xaml | 8 +- ExplorerEx/View/MainWindow.xaml.cs | 2 +- ExplorerEx/ViewModel/FileTabViewModel.cs | 87 +++++----- Readme.md | 9 +- 24 files changed, 328 insertions(+), 372 deletions(-) delete mode 100644 ExplorerEx/ExplorerEx - Backup.csproj diff --git a/Build.cmd b/Build.cmd index 7e54004..434b9d4 100644 --- a/Build.cmd +++ b/Build.cmd @@ -1 +1 @@ -dotnet build ./ExplorerEx/ExplorerEx.csproj -c Release \ No newline at end of file +dotnet build ExplorerEx -c Release \ No newline at end of file diff --git a/ExplorerEx/App.xaml b/ExplorerEx/App.xaml index 1bb9215..85add39 100644 --- a/ExplorerEx/App.xaml +++ b/ExplorerEx/App.xaml @@ -7,7 +7,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:m="clr-namespace:ExplorerEx.Model" xmlns:e="clr-namespace:ExplorerEx.Model.Enums" - xmlns:system="clr-namespace:System;assembly=System.Runtime" mc:Ignorable="d"> diff --git a/ExplorerEx/Command/FileItemCommand.cs b/ExplorerEx/Command/FileItemCommand.cs index e7e3178..a436d07 100644 --- a/ExplorerEx/Command/FileItemCommand.cs +++ b/ExplorerEx/Command/FileItemCommand.cs @@ -112,7 +112,8 @@ public class FileItemCommand : ICommand { } var destPaths = filePaths.Select(path => Path.Combine(Folder.FullPath, Path.GetFileName(path))).ToList(); try { - FileUtils.FileOperation(isCut ? FileOpType.Move : FileOpType.Copy, filePaths, destPaths); + await FileUtils.FileOperation(isCut ? FileOpType.Move : FileOpType.Copy, filePaths, destPaths); + FileTabControl.MouseOverTabControl.SelectedTab.FileListView.SelectItems(destPaths); } catch (Exception e) { Logger.Exception(e); } @@ -143,7 +144,7 @@ public class FileItemCommand : ICommand { return; } try { - 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); } diff --git a/ExplorerEx/ExplorerEx - Backup.csproj b/ExplorerEx/ExplorerEx - Backup.csproj deleted file mode 100644 index 7750a03..0000000 --- a/ExplorerEx/ExplorerEx - Backup.csproj +++ /dev/null @@ -1,123 +0,0 @@ - - - - WinExe - netcoreapp1.0 - disable - true - x64 - embedded - true - Assets\ExplorerEx.ico - app.manifest - False - - - - latest - - - - latest - - - - - - - - - - - - - - - - - - - - - Never - - - - - - PreserveNewest - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - Never - - - True - True - Resources.resx - - - Code - - - - - - Resources.resx - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - - MSBuild:Compile - - - - - - $(DefaultXamlRuntime) - Designer - - - - diff --git a/ExplorerEx/Model/CreateFileItem.cs b/ExplorerEx/Model/CreateFileItem.cs index 14b292a..a3b060a 100644 --- a/ExplorerEx/Model/CreateFileItem.cs +++ b/ExplorerEx/Model/CreateFileItem.cs @@ -34,19 +34,28 @@ public class CreateFileItem : SimpleNotifyPropertyChanged { } /// - /// 创建该文件,方法会自动枚举文件,防止重名 + /// 自动枚举文件获取下一个可以创建的文件 /// /// 文件夹路径 /// 创建的文件名,不包括路径 - public virtual string Create(string path) { - var fileName = FileUtils.GetNewFileName(path, $"{"New".L()} {Description}{Extension}"); - File.Create(Path.Combine(path, fileName)).Dispose(); - return fileName; + public virtual string GetCreateName(string path) { + return FileUtils.GetNewFileName(path, $"{"New".L()} {Description}{Extension}"); + } + + public virtual bool Create(string path, string fileName) { + var filePath = Path.Combine(path, fileName); + if (File.Exists(filePath)) { + return false; + } + File.Create(filePath).Dispose(); + return true; } public static ObservableCollection Items { get { - UpdateItems(); + if (items.Count == 0) { + UpdateItems(); + } return items; } } @@ -58,12 +67,11 @@ public class CreateFileItem : SimpleNotifyPropertyChanged { var newItems = (string[])Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Explorer\Discardable\PostSetup\ShellNew")!.GetValue("Classes")!; items.Clear(); items.Add(new CreateDirectoryItem()); - items.Add(new CreateFileLinkItem()); - items.Add(new CreateFileItem(".txt")); + // items.Add(new CreateFileLinkItem()); + // items.Add(new CreateFileItem(".txt")); foreach (var item in newItems) { if (item[0] == '.') { switch (item) { - case ".txt": case ".lnk": case ".library-ms": continue; @@ -85,10 +93,17 @@ internal class CreateDirectoryItem : CreateFileItem { Description = "Folder".L(); } - public override string Create(string path) { - var folderName = FileUtils.GetNewFileName(path, "New_folder".L()); - Directory.CreateDirectory(Path.Combine(path, folderName)); - return folderName; + public override string GetCreateName(string path) { + return FileUtils.GetNewFileName(path, "New_folder".L()); + } + + public override bool Create(string path, string folderName) { + var folderPath = Path.Combine(path, folderName); + if (Directory.Exists(folderPath)) { + return false; + } + Directory.CreateDirectory(folderPath); + return true; } } diff --git a/ExplorerEx/Model/FileItem/FileListViewItem.cs b/ExplorerEx/Model/FileItem/FileListViewItem.cs index 5d5a01c..0c702e5 100644 --- a/ExplorerEx/Model/FileItem/FileListViewItem.cs +++ b/ExplorerEx/Model/FileItem/FileListViewItem.cs @@ -140,7 +140,7 @@ public abstract class FileListViewItem : SimpleNotifyPropertyChanged { /// /// /// - /// + /// /// public static async Task LoadDetails(IReadOnlyCollection list, CancellationToken token, LoadDetailsOptions options) { try { diff --git a/ExplorerEx/Strings/Resources.Designer.cs b/ExplorerEx/Strings/Resources.Designer.cs index ee44ccd..69fd84f 100644 --- a/ExplorerEx/Strings/Resources.Designer.cs +++ b/ExplorerEx/Strings/Resources.Designer.cs @@ -754,6 +754,15 @@ internal class Resources { } } + /// + /// 查找类似 文件或文件夹已存在。 的本地化字符串。 + /// + internal static string FileOrFolderAlreadyExist { + get { + return ResourceManager.GetString("FileOrFolderAlreadyExist", resourceCulture); + } + } + /// /// 查找类似 文件大小 的本地化字符串。 /// diff --git a/ExplorerEx/Strings/Resources.en.resx b/ExplorerEx/Strings/Resources.en.resx index 6930366..632ca85 100644 --- a/ExplorerEx/Strings/Resources.en.resx +++ b/ExplorerEx/Strings/Resources.en.resx @@ -542,4 +542,7 @@ For the application to run properly, it's recommanded that you first extract all Error occurs when extracting following files: + + File or Folder already exist. + \ No newline at end of file diff --git a/ExplorerEx/Strings/Resources.resx b/ExplorerEx/Strings/Resources.resx index f9bc33e..700b234 100644 --- a/ExplorerEx/Strings/Resources.resx +++ b/ExplorerEx/Strings/Resources.resx @@ -589,4 +589,7 @@ ExplorerEx [-h|--help] [-r|--runInBackground] [-o|--openInNewWindow] [-d|--requi 完整路径 + + 文件或文件夹已存在。 + \ No newline at end of file diff --git a/ExplorerEx/Utils/Collections/ConcurrentObservableCollection.cs b/ExplorerEx/Utils/Collections/ConcurrentObservableCollection.cs index 50cd455..c5ab305 100644 --- a/ExplorerEx/Utils/Collections/ConcurrentObservableCollection.cs +++ b/ExplorerEx/Utils/Collections/ConcurrentObservableCollection.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Collections; using System.Collections.Generic; @@ -13,18 +14,18 @@ namespace ExplorerEx.Utils.Collections; /// Thread-safe collection. You can safely bind it to a WPF control using the property . /// public sealed class ConcurrentObservableCollection : IList, IReadOnlyList, IList { - private readonly Dispatcher _dispatcher; - private readonly object _lock = new(); + private readonly Dispatcher dispatcher; + private readonly object lockObj = new(); - private ImmutableList _items = ImmutableList.Empty; - private DispatchedObservableCollection? _observableCollection; + private ImmutableList items = ImmutableList.Empty; + private DispatchedObservableCollection? observableCollection; public ConcurrentObservableCollection() : this(GetCurrentDispatcher()) { } public ConcurrentObservableCollection(Dispatcher dispatcher) { - _dispatcher = dispatcher; + this.dispatcher = dispatcher; } private static Dispatcher GetCurrentDispatcher() { @@ -37,21 +38,21 @@ public ConcurrentObservableCollection() /// Most WPF controls doesn't support batch modifications public bool SupportRangeNotifications { get; set; } - public IReadOnlyObservableCollection AsObservable { + public DispatchedObservableCollection AsObservable { get { - if (_observableCollection == null) { - lock (_lock) { - _observableCollection ??= new DispatchedObservableCollection(this, _dispatcher); + if (observableCollection == null) { + lock (lockObj) { + observableCollection ??= new DispatchedObservableCollection(this, dispatcher); } } - return _observableCollection; + return observableCollection; } } bool ICollection.IsReadOnly => false; - public int Count => _items.Count; + public int Count => items.Count; bool IList.IsReadOnly => false; @@ -59,9 +60,9 @@ public ConcurrentObservableCollection() int ICollection.Count => Count; - object ICollection.SyncRoot => ((ICollection)_items).SyncRoot; + object ICollection.SyncRoot => ((ICollection)items).SyncRoot; - bool ICollection.IsSynchronized => ((ICollection)_items).IsSynchronized; + bool ICollection.IsSynchronized => ((ICollection)items).IsSynchronized; object? IList.this[int index] { get => this[index]; @@ -72,21 +73,19 @@ public ConcurrentObservableCollection() } public T this[int index] { - get => _items[index]; + get => items[index]; set { - lock (_lock) { - _items = _items.SetItem(index, value); - if (_observableCollection != null) { - _observableCollection.EnqueueReplace(index, value); - } + lock (lockObj) { + items = items.SetItem(index, value); + observableCollection?.EnqueueReplace(index, value); } } } public void Add(T item) { - lock (_lock) { - _items = _items.Add(item); - _observableCollection?.EnqueueAdd(item); + lock (lockObj) { + items = items.Add(item); + observableCollection?.EnqueueAdd(item); } } @@ -95,15 +94,15 @@ public ConcurrentObservableCollection() } public void AddRange(IEnumerable items) { - lock (_lock) { - var count = _items.Count; - _items = _items.AddRange(items); + lock (lockObj) { + var count = this.items.Count; + this.items = this.items.AddRange(items); if (SupportRangeNotifications) { - _observableCollection?.EnqueueAddRange(_items.GetRange(count, _items.Count - count)); + observableCollection?.EnqueueAddRange(this.items.GetRange(count, this.items.Count - count)); } else { - if (_observableCollection != null) { - for (var i = count; i < _items.Count; i++) { - _observableCollection.EnqueueAdd(_items[i]); + if (observableCollection != null) { + for (var i = count; i < this.items.Count; i++) { + observableCollection.EnqueueAdd(this.items[i]); } } } @@ -111,16 +110,16 @@ public ConcurrentObservableCollection() } public void InsertRange(int index, IEnumerable items) { - lock (_lock) { - var count = _items.Count; - _items = _items.InsertRange(index, items); - var addedItemsCount = _items.Count - count; + lock (lockObj) { + var count = this.items.Count; + this.items = this.items.InsertRange(index, items); + var addedItemsCount = this.items.Count - count; if (SupportRangeNotifications) { - _observableCollection?.EnqueueInsertRange(index, _items.GetRange(index, addedItemsCount)); + observableCollection?.EnqueueInsertRange(index, this.items.GetRange(index, addedItemsCount)); } else { - if (_observableCollection != null) { + if (observableCollection != null) { for (var i = index; i < index + addedItemsCount; i++) { - _observableCollection.EnqueueInsert(i, _items[i]); + observableCollection.EnqueueInsert(i, this.items[i]); } } } @@ -128,25 +127,32 @@ public ConcurrentObservableCollection() } public void Clear() { - lock (_lock) { - _items = _items.Clear(); - _observableCollection?.EnqueueClear(); + lock (lockObj) { + items = items.Clear(); + observableCollection?.EnqueueClear(); + } + } + + public void Reset(IEnumerable items) { + lock (lockObj) { + this.items = ImmutableList.Empty.AddRange(items); + observableCollection?.EnqueueReset(this.items); } } public void Insert(int index, T item) { - lock (_lock) { - _items = _items.Insert(index, item); - _observableCollection?.EnqueueInsert(index, item); + lock (lockObj) { + items = items.Insert(index, item); + observableCollection?.EnqueueInsert(index, item); } } public bool Remove(T item) { - lock (_lock) { - var newList = _items.Remove(item); - if (_items != newList) { - _items = newList; - _observableCollection?.EnqueueRemove(item); + lock (lockObj) { + var newList = items.Remove(item); + if (items != newList) { + items = newList; + observableCollection?.EnqueueRemove(item); return true; } @@ -155,14 +161,14 @@ public ConcurrentObservableCollection() } public void RemoveAt(int index) { - lock (_lock) { - _items = _items.RemoveAt(index); - _observableCollection?.EnqueueRemoveAt(index); + lock (lockObj) { + items = items.RemoveAt(index); + observableCollection?.EnqueueRemoveAt(index); } } public IEnumerator GetEnumerator() { - return _items.GetEnumerator(); + return items.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { @@ -170,46 +176,38 @@ public ConcurrentObservableCollection() } public int IndexOf(T item) { - return _items.IndexOf(item); + return items.IndexOf(item); } public bool Contains(T item) { - return _items.Contains(item); + return items.Contains(item); } public void CopyTo(T[] array, int arrayIndex) { - _items.CopyTo(array, arrayIndex); + items.CopyTo(array, arrayIndex); } - public void Sort() { - Sort(comparer: null); - } - - public void Sort(IComparer? comparer) { - lock (_lock) { - _items = _items.Sort(comparer); - _observableCollection?.EnqueueReset(_items); + public void Sort(IComparer? comparer = null) { + lock (lockObj) { + items = items.Sort(comparer); + observableCollection?.EnqueueReset(items); } } - public void StableSort() { - StableSort(comparer: null); - } - - public void StableSort(IComparer? comparer) { - lock (_lock) { - _items = ImmutableList.CreateRange(_items.OrderBy(item => item, comparer)); - _observableCollection?.EnqueueReset(_items); + public void StableSort(IComparer? comparer = null) { + lock (lockObj) { + items = ImmutableList.CreateRange(items.OrderBy(item => item, comparer)); + observableCollection?.EnqueueReset(items); } } int IList.Add(object? value) { AssertType(value, nameof(value)); var item = (T)value!; - lock (_lock) { - var index = _items.Count; - _items = _items.Add(item); - _observableCollection?.EnqueueAdd(item); + lock (lockObj) { + var index = items.Count; + items = items.Add(item); + observableCollection?.EnqueueAdd(item); return index; } } @@ -243,12 +241,13 @@ public ConcurrentObservableCollection() } void ICollection.CopyTo(Array array, int index) { - ((ICollection)_items).CopyTo(array, index); + ((ICollection)items).CopyTo(array, index); } private static void AssertType(object? value, string argumentName) { - if (value is null || value is T) + if (value is null or T) { return; + } throw new ArgumentException($"value must be of type '{typeof(T).FullName}'", argumentName); } diff --git a/ExplorerEx/Utils/Collections/Internals/DispatchedObservableCollection.cs b/ExplorerEx/Utils/Collections/Internals/DispatchedObservableCollection.cs index 9d995d6..ad4331a 100644 --- a/ExplorerEx/Utils/Collections/Internals/DispatchedObservableCollection.cs +++ b/ExplorerEx/Utils/Collections/Internals/DispatchedObservableCollection.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Collections; using System.Collections.Concurrent; @@ -9,28 +10,29 @@ namespace ExplorerEx.Utils.Collections.Internals; public sealed class DispatchedObservableCollection : ObservableCollectionBase, IReadOnlyObservableCollection, IList, IList { - private readonly ConcurrentQueue> _pendingEvents = new(); - private readonly ConcurrentObservableCollection _collection; - private readonly Dispatcher _dispatcher; + private readonly ConcurrentQueue> pendingEvents = new(); + private readonly ConcurrentObservableCollection collection; + private readonly Dispatcher dispatcher; - private bool _isDispatcherPending; + private bool isDispatcherPending; public DispatchedObservableCollection(ConcurrentObservableCollection collection, Dispatcher dispatcher) : base(collection) { - _collection = collection; - _dispatcher = dispatcher; + this.collection = collection; + this.dispatcher = dispatcher; } private void AssertIsOnDispatcherThread() { - if (!IsOnDispatcherThread()) { + if (!dispatcher.CheckAccess()) { var currentThreadId = Environment.CurrentManagedThreadId; throw new InvalidOperationException("The collection must be accessed from the dispatcher thread only. Current thread ID: " + currentThreadId.ToString(CultureInfo.InvariantCulture)); } } private static void AssertType(object? value, string argumentName) { - if (value is null || value is T) + if (value is null or T) { return; + } throw new ArgumentException($"value must be of type '{typeof(T).FullName}'", argumentName); } @@ -45,7 +47,7 @@ public DispatchedObservableCollection(ConcurrentObservableCollection collecti bool ICollection.IsReadOnly { get { AssertIsOnDispatcherThread(); - return ((ICollection)_collection).IsReadOnly; + return ((ICollection)collection).IsReadOnly; } } @@ -94,7 +96,7 @@ public DispatchedObservableCollection(ConcurrentObservableCollection collecti // it will immediately modify both collections as we are on the dispatcher thread AssertType(value, nameof(value)); AssertIsOnDispatcherThread(); - _collection[index] = (T)value!; + collection[index] = (T)value!; } } @@ -106,7 +108,7 @@ public DispatchedObservableCollection(ConcurrentObservableCollection collecti set { // it will immediately modify both collections as we are on the dispatcher thread AssertIsOnDispatcherThread(); - _collection[index] = value; + collection[index] = value; } } @@ -177,15 +179,15 @@ public DispatchedObservableCollection(ConcurrentObservableCollection collecti } private void EnqueueEvent(PendingEvent @event) { - _pendingEvents.Enqueue(@event); + pendingEvents.Enqueue(@event); ProcessPendingEventsOrDispatch(); } private void ProcessPendingEventsOrDispatch() { - if (!IsOnDispatcherThread()) { - if (!_isDispatcherPending) { - _isDispatcherPending = true; - _dispatcher.BeginInvoke(ProcessPendingEvents); + if (!dispatcher.CheckAccess()) { + if (!isDispatcherPending) { + isDispatcherPending = true; + dispatcher.BeginInvoke(ProcessPendingEvents); } return; @@ -195,15 +197,15 @@ public DispatchedObservableCollection(ConcurrentObservableCollection collecti } private void ProcessPendingEvents() { - _isDispatcherPending = false; - while (_pendingEvents.TryDequeue(out var pendingEvent)) { + isDispatcherPending = false; + while (pendingEvents.TryDequeue(out var pendingEvent)) { switch (pendingEvent.Type) { case PendingEventType.Add: AddItem(pendingEvent.Item); break; case PendingEventType.AddRange: - AddItems(pendingEvent.Items); + AddItems(pendingEvent.Items!); break; case PendingEventType.Remove: @@ -219,7 +221,7 @@ public DispatchedObservableCollection(ConcurrentObservableCollection collecti break; case PendingEventType.InsertRange: - InsertItems(pendingEvent.Index, pendingEvent.Items); + InsertItems(pendingEvent.Index, pendingEvent.Items!); break; case PendingEventType.RemoveAt: @@ -231,44 +233,40 @@ public DispatchedObservableCollection(ConcurrentObservableCollection collecti break; case PendingEventType.Reset: - Reset(pendingEvent.Items); + Reset(pendingEvent.Items!); break; } } } - private bool IsOnDispatcherThread() { - return _dispatcher.Thread == Thread.CurrentThread; - } - void IList.Insert(int index, T item) { // it will immediately modify both collections as we are on the dispatcher thread AssertIsOnDispatcherThread(); - _collection.Insert(index, item); + collection.Insert(index, item); } void IList.RemoveAt(int index) { // it will immediately modify both collections as we are on the dispatcher thread AssertIsOnDispatcherThread(); - _collection.RemoveAt(index); + collection.RemoveAt(index); } void ICollection.Add(T item) { // it will immediately modify both collections as we are on the dispatcher thread AssertIsOnDispatcherThread(); - _collection.Add(item); + collection.Add(item); } void ICollection.Clear() { // it will immediately modify both collections as we are on the dispatcher thread AssertIsOnDispatcherThread(); - _collection.Clear(); + collection.Clear(); } bool ICollection.Remove(T item) { // it will immediately modify both collections as we are on the dispatcher thread AssertIsOnDispatcherThread(); - return _collection.Remove(item); + return collection.Remove(item); } void ICollection.CopyTo(Array array, int index) { @@ -279,20 +277,20 @@ public DispatchedObservableCollection(ConcurrentObservableCollection collecti // it will immediately modify both collections as we are on the dispatcher thread AssertType(value, nameof(value)); AssertIsOnDispatcherThread(); - return ((IList)_collection).Add(value); + return ((IList)collection).Add(value); } bool IList.Contains(object? value) { // it will immediately modify both collections as we are on the dispatcher thread AssertType(value, nameof(value)); AssertIsOnDispatcherThread(); - return ((IList)_collection).Contains(value); + return ((IList)collection).Contains(value); } void IList.Clear() { // it will immediately modify both collections as we are on the dispatcher thread AssertIsOnDispatcherThread(); - ((IList)_collection).Clear(); + ((IList)collection).Clear(); } int IList.IndexOf(object? value) { @@ -306,19 +304,19 @@ public DispatchedObservableCollection(ConcurrentObservableCollection collecti // it will immediately modify both collections as we are on the dispatcher thread AssertType(value, nameof(value)); AssertIsOnDispatcherThread(); - ((IList)_collection).Insert(index, value); + ((IList)collection).Insert(index, value); } void IList.Remove(object? value) { // it will immediately modify both collections as we are on the dispatcher thread AssertType(value, nameof(value)); AssertIsOnDispatcherThread(); - ((IList)_collection).Remove(value); + ((IList)collection).Remove(value); } void IList.RemoveAt(int index) { // it will immediately modify both collections as we are on the dispatcher thread AssertIsOnDispatcherThread(); - ((IList)_collection).RemoveAt(index); + ((IList)collection).RemoveAt(index); } } diff --git a/ExplorerEx/Utils/Collections/Internals/ObservableCollectionBase.cs b/ExplorerEx/Utils/Collections/Internals/ObservableCollectionBase.cs index 52034f4..327da46 100644 --- a/ExplorerEx/Utils/Collections/Internals/ObservableCollectionBase.cs +++ b/ExplorerEx/Utils/Collections/Internals/ObservableCollectionBase.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.Specialized; @@ -16,22 +17,17 @@ public abstract class ObservableCollectionBase : INotifyCollectionChanged, IN Items = new List(); } - protected ObservableCollectionBase(IEnumerable items) { + protected ObservableCollectionBase(IEnumerable? items) { if (items == null) { Items = new List(); } else { Items = new List(items); } } - -#if NET6_0_OR_GREATER + public void EnsureCapacity(int capacity) { Items.EnsureCapacity(capacity); } -#elif NET5_0 || NETCOREAPP3_1 || NET461 -#else -#error Platform not supported -#endif protected void ReplaceItem(int index, T item) { var oldItem = Items[index]; @@ -86,8 +82,9 @@ public abstract class ObservableCollectionBase : INotifyCollectionChanged, IN protected bool RemoveItem(T item) { var index = Items.IndexOf(item); - if (index < 0) + if (index < 0) { return false; + } Items.RemoveAt(index); diff --git a/ExplorerEx/Utils/Collections/Internals/PendingEvent`1.cs b/ExplorerEx/Utils/Collections/Internals/PendingEvent`1.cs index c81e36a..74c5f0c 100644 --- a/ExplorerEx/Utils/Collections/Internals/PendingEvent`1.cs +++ b/ExplorerEx/Utils/Collections/Internals/PendingEvent`1.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Collections.Immutable; using System.Runtime.InteropServices; diff --git a/ExplorerEx/Utils/FileUtils.cs b/ExplorerEx/Utils/FileUtils.cs index fa5d605..775b27d 100644 --- a/ExplorerEx/Utils/FileUtils.cs +++ b/ExplorerEx/Utils/FileUtils.cs @@ -220,8 +220,8 @@ internal static class FileUtils { /// /// /// - public static void FileOperation(FileOpType type, IList sourceFiles, IList destinationFiles = null) { - if (sourceFiles is not {Count: > 0}) { + public static Task FileOperation(FileOpType type, IList sourceFiles, IList destinationFiles = null) { + if (sourceFiles is not { Count: > 0 }) { throw new ArgumentException("原文件个数不能为0"); } if (type != FileOpType.Delete && (destinationFiles == null || sourceFiles.Count != destinationFiles.Count)) { @@ -247,7 +247,7 @@ internal static class FileUtils { if (type != FileOpType.Delete) { fo.pTo = ParseFileList(destinationFiles); } - Task.Run(() => { + return Task.Run(() => { var result = Shell32Interop.SHFileOperation(fo); if (result is not 0 and not 1223) { // 1223: 用户取消了操作 throw new IOException(GetErrorPrompting(result)); @@ -264,7 +264,7 @@ internal static class FileUtils { /// /// /// - public static void FileOperation(FileOpType type, string sourceFile, string destinationFile = null) { + public static Task FileOperation(FileOpType type, string sourceFile, string destinationFile = null) { if (sourceFile == null) { throw new ArgumentNullException(nameof(sourceFile)); } @@ -288,7 +288,7 @@ internal static class FileUtils { if (type != FileOpType.Delete) { fo.pTo = destinationFile + '\0'; } - Task.Run(() => { + return Task.Run(() => { var result = Shell32Interop.SHFileOperation(fo); if (result is not 0 and not 1223) { // 1223: 用户取消了操作 throw new IOException(GetErrorPrompting(result)); @@ -305,9 +305,19 @@ internal static class FileUtils { } - public static void HandleDrop(DataObjectContent content, string destPath, DragDropEffects type) { + /// + /// 处理拖放事件,返回受影响的文件列表 + /// + /// + /// + /// + /// + public static async Task HandleDrop(DataObjectContent content, string destPath, DragDropEffects type) { + if (content == null) { + throw new ArgumentNullException(nameof(content)); + } if (destPath == null) { - return; + throw new ArgumentNullException(nameof(destPath)); } Debug.Assert(type is DragDropEffects.Copy or DragDropEffects.Move or DragDropEffects.Link); if (destPath.Length > 4 && destPath[^4..] is ".exe" or ".lnk") { // 拖文件运行 @@ -318,6 +328,7 @@ internal static class FileUtils { Arguments = string.Join(' ', (string[])content.Data), UseShellExecute = true }); + return new[] { destPath }; } catch (Exception ex) { Logger.Exception(ex); } @@ -331,21 +342,23 @@ internal static class FileUtils { case DataObjectType.FileDrop: var filePaths = (string[])content.Data; if (filePaths is { Length: > 0 }) { - var destPaths = filePaths.Select(p => Path.Combine(destPath, Path.GetFileName(p))).ToList(); + var destPaths = filePaths.Select(p => Path.Combine(destPath, Path.GetFileName(p))).ToArray(); try { if (type == DragDropEffects.Link) { for (var i = 0; i < filePaths.Length; i++) { - var path = destPaths[i]; - if (path.Length == 3) { // 处理驱动器号这种特殊情况 - path = path + filePaths[i][0] + ".lnk"; + var lnkPath = destPaths[i]; + if (lnkPath.Length == 3) { // 处理驱动器号这种特殊情况 + lnkPath = lnkPath + filePaths[i][0] + ".lnk"; } else { - path = Path.ChangeExtension(path, ".lnk"); + lnkPath = Path.ChangeExtension(lnkPath, ".lnk"); } - Shell32Interop.CreateLnk(filePaths[i], path); + Shell32Interop.CreateLnk(filePaths[i], lnkPath); + destPaths[i] = lnkPath; } } else { - FileOperation(type == DragDropEffects.Move ? FileOpType.Move : FileOpType.Copy, filePaths, destPaths); + await FileOperation(type == DragDropEffects.Move ? FileOpType.Move : FileOpType.Copy, filePaths, destPaths); } + return destPaths; } catch (Exception ex) { Logger.Exception(ex); } @@ -365,6 +378,7 @@ internal static class FileUtils { } } } + return null; } private static string ParseFileList(IEnumerable files) { diff --git a/ExplorerEx/View/Controls/CustomControls.xaml b/ExplorerEx/View/Controls/CustomControls.xaml index a8580b8..ba9853e 100644 --- a/ExplorerEx/View/Controls/CustomControls.xaml +++ b/ExplorerEx/View/Controls/CustomControls.xaml @@ -40,7 +40,7 @@ - @@ -201,7 +201,7 @@ - + diff --git a/ExplorerEx/View/Controls/FileListView.xaml b/ExplorerEx/View/Controls/FileListView.xaml index 074c77a..477a768 100644 --- a/ExplorerEx/View/Controls/FileListView.xaml +++ b/ExplorerEx/View/Controls/FileListView.xaml @@ -387,13 +387,13 @@ - + - + @@ -404,8 +404,7 @@ - + ItemContainerStyle="{StaticResource CreateMenuItemContainerStyle}"/> diff --git a/ExplorerEx/View/Controls/FileListView.xaml.cs b/ExplorerEx/View/Controls/FileListView.xaml.cs index 9f4f9f6..9ea9d67 100644 --- a/ExplorerEx/View/Controls/FileListView.xaml.cs +++ b/ExplorerEx/View/Controls/FileListView.xaml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.Collections.Specialized; @@ -10,7 +11,6 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Input; using System.Windows.Threading; @@ -22,10 +22,10 @@ using ExplorerEx.Model.Enums; using ExplorerEx.Shell32; using ExplorerEx.Utils; +using ExplorerEx.Utils.Collections.Internals; using ExplorerEx.ViewModel; using ExplorerEx.Win32; using HandyControl.Controls; -using HandyControl.Data; using HandyControl.Tools; using GridView = System.Windows.Controls.GridView; using ScrollViewer = System.Windows.Controls.ScrollViewer; @@ -43,14 +43,14 @@ public partial class FileListView : INotifyPropertyChanged { private static string[] draggingPaths; public new static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register( - "ItemsSource", typeof(ObservableCollection), typeof(FileListView), new PropertyMetadata(ItemsSource_OnChanged)); + "ItemsSource", typeof(DispatchedObservableCollection), typeof(FileListView), new PropertyMetadata(ItemsSource_OnChanged)); private static void ItemsSource_OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var fileGrid = (FileListView)d; - if (e.OldValue is ObservableCollection oldList) { + if (e.OldValue is DispatchedObservableCollection oldList) { oldList.CollectionChanged -= fileGrid.OnItemsChanged; } - if (e.NewValue is ObservableCollection newList) { + if (e.NewValue is DispatchedObservableCollection newList) { newList.CollectionChanged += fileGrid.OnItemsChanged; ((ItemsControl)fileGrid).ItemsSource = newList; } else { @@ -58,12 +58,22 @@ public partial class FileListView : INotifyPropertyChanged { } } - public new ObservableCollection ItemsSource { - get => (ObservableCollection)GetValue(ItemsSourceProperty); + public new DispatchedObservableCollection ItemsSource { + get => (DispatchedObservableCollection)GetValue(ItemsSourceProperty); set => SetValue(ItemsSourceProperty, value); } - public FileTabViewModel ViewModel { get; private set; } + public FileTabViewModel ViewModel { + get => viewModel; + private set { + if (viewModel != value) { + viewModel = value; + UpdateUI(); + } + } + } + + private FileTabViewModel viewModel; public static readonly DependencyProperty OwnerWindowProperty = DependencyProperty.Register( "OwnerWindow", typeof(MainWindow), typeof(FileListView), new PropertyMetadata(default(MainWindow))); @@ -186,7 +196,18 @@ public partial class FileListView : INotifyPropertyChanged { } } + /// + /// 用于让SelectionChanged方法判断是不是切换标签页了,如果是就不通知ViewModel改变 + /// + 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; @@ -334,8 +355,6 @@ public partial class FileListView : INotifyPropertyChanged { private Point startSelectionPoint; private DispatcherTimer timer; - - private FileListViewItem renamingItem; private bool shouldRename; private void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e) { @@ -353,6 +372,20 @@ public partial class FileListView : INotifyPropertyChanged { } } + /// + /// 按照文件名选中一批文件并滚动到 + /// + /// 不是完整路径 + public void SelectItems(IEnumerable fileNames) { + foreach (var fileName in fileNames) { + var newItem = ViewModel.AddSingleItem(fileName); + if (newItem != null) { + ScrollIntoView(newItem); + newItem.IsSelected = true; + } + } + } + /// /// 用于处理双击事件 /// @@ -445,7 +478,7 @@ public partial class FileListView : INotifyPropertyChanged { } else if (!item.IsSelected) { UnselectAll(); item.IsSelected = true; - } else if (selectedItems.Count == 1 && renamingItem == null) { + } else if (selectedItems.Count == 1) { isPreparedForRenaming = true; } } @@ -498,7 +531,7 @@ public partial class FileListView : INotifyPropertyChanged { } else { MouseItem = null; } - if (!isMouseDown || isDoubleClicked || isDragDropping || renamingItem != null) { + if (!isMouseDown || isDoubleClicked || isDragDropping) { return; } // 只有isMouseDown(即OnPreviewMouseDown触发过)为true,这个才有用 @@ -602,7 +635,7 @@ public partial class FileListView : INotifyPropertyChanged { break; } isMouseDown = false; - if (isDoubleClicked || renamingItem != null) { // 如果双击了或者正在重命名就不处理 + if (isDoubleClicked) { // 如果双击了就不处理 break; } if (e.ChangedButton is MouseButton.Left or MouseButton.Right) { @@ -629,6 +662,7 @@ public partial class FileListView : INotifyPropertyChanged { if (shouldRename) { ViewModel.StartRename(item); } + shouldRename = false; }); } else { shouldRename = false; @@ -686,6 +720,17 @@ public partial class FileListView : INotifyPropertyChanged { mouseDownRowIndex = -1; } + protected override void OnSelectionChanged(SelectionChangedEventArgs e) { + base.OnSelectionChanged(e); + + if (isDataContextChanging) { + isDataContextChanging = false; + return; + } + + viewModel.ChangeSelection(e); + } + protected override void OnPreviewMouseWheel(MouseWheelEventArgs e) { if (previewPopup is { IsOpen: true }) { previewPopup.HandleMouseScroll(e); @@ -837,7 +882,7 @@ public partial class FileListView : INotifyPropertyChanged { } } - protected override void OnDrop(DragEventArgs e) { + protected override async void OnDrop(DragEventArgs e) { isDragDropping = false; var path = e.OriginalSource is DependencyObject d ? ContainerFromElement(d) switch { ListBoxItem i => ((FileListViewItem)i.Content).FullPath, @@ -847,7 +892,12 @@ public partial class FileListView : INotifyPropertyChanged { if (path == null) { return; } - FileUtils.HandleDrop(DataObjectContent.Drag, path, GetEffectWithKeyboard(e.Effects)); + try { + var affectedItems = await FileUtils.HandleDrop(DataObjectContent.Drag, path, GetEffectWithKeyboard(e.Effects)); + SelectItems(affectedItems.Select(Path.GetFileName)); + } catch (Exception ex) { + Logger.Exception(ex); + } } protected override void OnPreviewGiveFeedback(GiveFeedbackEventArgs e) { diff --git a/ExplorerEx/View/Controls/FileViewGrid.xaml b/ExplorerEx/View/Controls/FileViewGrid.xaml index cfbcce0..5c7a185 100644 --- a/ExplorerEx/View/Controls/FileViewGrid.xaml +++ b/ExplorerEx/View/Controls/FileViewGrid.xaml @@ -8,7 +8,6 @@ xmlns:ct="clr-namespace:ExplorerEx.View.Controls" xmlns:hc="https://handyorg.github.io/handycontrol" xmlns:vm="clr-namespace:ExplorerEx.ViewModel" - xmlns:c="clr-namespace:ExplorerEx.Converter" xmlns:e="clr-namespace:ExplorerEx.Model.Enums" mc:Ignorable="d" d:DataContext="{d:DesignInstance vm:FileTabViewModel}"> @@ -144,13 +143,13 @@ - + @@ -223,16 +222,13 @@ BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}" Background="{DynamicResource RegionBrush}"> - - - @@ -243,7 +239,7 @@ - + diff --git a/ExplorerEx/View/Controls/SplitGrid.xaml.cs b/ExplorerEx/View/Controls/SplitGrid.xaml.cs index efa4a7f..e059887 100644 --- a/ExplorerEx/View/Controls/SplitGrid.xaml.cs +++ b/ExplorerEx/View/Controls/SplitGrid.xaml.cs @@ -67,14 +67,12 @@ public partial class SplitGrid : IEnumerable { /// private bool IsTopRightGrid { set { - if (isTopRightGrid != value) { - isTopRightGrid = value; - if (value) { - FileTabControl.TabBorderRootMargin = new Thickness(0, 0, 160, 0); - } else { - FileTabControl.TabBorderRootMargin = new Thickness(); - } + if (value) { + FileTabControl.TabBorderRootMargin = new Thickness(0, 0, 130, 0); + } else { + FileTabControl.TabBorderRootMargin = new Thickness(); } + isTopRightGrid = value; } } @@ -93,6 +91,7 @@ public partial class SplitGrid : IEnumerable { MainWindow = mainWindow; this.ownerSplitGrid = ownerSplitGrid; InitializeComponent(); + #if DEBUG Name = $"SplitGrid{id++}"; #endif @@ -101,10 +100,10 @@ public partial class SplitGrid : IEnumerable { public SplitGrid(MainWindow mainWindow, SplitGrid ownerSplitGrid, FileTabViewModel tab = null) : this(mainWindow, ownerSplitGrid) { FileTabControl = new FileTabControl(mainWindow, this, tab); Children.Insert(0, FileTabControl); + FileTabControl.UpdateTabContextMenu(); if (ownerSplitGrid == null) { IsTopRightGrid = true; } - FileTabControl.UpdateTabContextMenu(); } public SplitGrid(MainWindow mainWindow, SplitGrid ownerSplitGrid, FileTabControl tab) : this(mainWindow, ownerSplitGrid) { @@ -112,6 +111,9 @@ public partial class SplitGrid : IEnumerable { FileTabControl.OwnerSplitGrid = this; Children.Insert(0, tab); FileTabControl.UpdateTabContextMenu(); + if (ownerSplitGrid == null) { + IsTopRightGrid = true; + } } /// @@ -174,8 +176,8 @@ public partial class SplitGrid : IEnumerable { FirstSplit(); otherSplitGrid = new SplitGrid(MainWindow, this, tab); if (isTopRightGrid) { - otherSplitGrid.isTopRightGrid = true; - isTopRightGrid = false; + otherSplitGrid.IsTopRightGrid = true; + IsTopRightGrid = false; FileTabControl.TabBorderRoot.Margin = new Thickness(); } otherSplitGrid.SetValue(ColumnProperty, 2); diff --git a/ExplorerEx/View/Controls/TabControl/FileTabItem.cs b/ExplorerEx/View/Controls/TabControl/FileTabItem.cs index 6317b5a..fafde5c 100644 --- a/ExplorerEx/View/Controls/TabControl/FileTabItem.cs +++ b/ExplorerEx/View/Controls/TabControl/FileTabItem.cs @@ -280,7 +280,7 @@ public class FileTabItem : TabItem { maxMoveLeft = -mouseDownIndex * ItemWidth - mouseDownOffsetX; maxMoveRight = parent.ActualWidth - ActualWidth + maxMoveLeft; - Trace.WriteLine(maxMoveLeft + ", " + maxMoveRight); + // Trace.WriteLine(maxMoveLeft + ", " + maxMoveRight); isDragging = true; isItemDragging = true; isWaiting = true; @@ -338,6 +338,7 @@ public class FileTabItem : TabItem { RenderTransform = new TranslateTransform(left, 0); dragPoint = p; + return; if (p.X < 0 || p.X > parent.ActualWidth || p.Y < -10 || p.Y > parent.ActualHeight + 10) { // 开始拖动标签页 DraggingFileTab = this; Opacity = 0d; // 不能设置Visibility = Hidden; 会导致MouseCapture被取消 diff --git a/ExplorerEx/View/MainWindow.xaml b/ExplorerEx/View/MainWindow.xaml index b9ec2a6..61c2050 100644 --- a/ExplorerEx/View/MainWindow.xaml +++ b/ExplorerEx/View/MainWindow.xaml @@ -487,23 +487,23 @@ - + --> - + - 0.5,0.5 diff --git a/ExplorerEx/View/MainWindow.xaml.cs b/ExplorerEx/View/MainWindow.xaml.cs index 5ff6cb4..f1084d5 100644 --- a/ExplorerEx/View/MainWindow.xaml.cs +++ b/ExplorerEx/View/MainWindow.xaml.cs @@ -575,7 +575,7 @@ public sealed partial class MainWindow { if (ShowMessageBox() == MessageBoxResult.OK && !RenameTextBox.IsError) { return RenameTextBox.Text; } - return originalName; + return null; } #endregion diff --git a/ExplorerEx/ViewModel/FileTabViewModel.cs b/ExplorerEx/ViewModel/FileTabViewModel.cs index 019d5f1..b9c0be3 100644 --- a/ExplorerEx/ViewModel/FileTabViewModel.cs +++ b/ExplorerEx/ViewModel/FileTabViewModel.cs @@ -16,11 +16,11 @@ using ExplorerEx.Model.Enums; using ExplorerEx.Shell32; using ExplorerEx.Utils; +using ExplorerEx.Utils.Collections; using ExplorerEx.View; using ExplorerEx.View.Controls; using ExplorerEx.Win32; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; using static ExplorerEx.Model.FileListViewItem; using static ExplorerEx.View.Controls.FileListView; using hc = HandyControl.Controls; @@ -93,7 +93,7 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { /// /// 当前文件夹内的文件列表 /// - public ObservableCollection Items { get; } = new(); + public ConcurrentObservableCollection Items { get; } = new(); /// /// 当前文件夹是否在加载 @@ -110,9 +110,7 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { private bool isLoading; - public ObservableHashSet SelectedItems { get; } = new(); - - public SimpleCommand SelectionChangedCommand { get; } + public HashSet SelectedItems { get; } = new(); public bool CanGoBack => nextHistoryIndex > 1; @@ -201,8 +199,6 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { private bool canPaste; - public int FileItemsCount => Items.Count; - public Visibility SelectedFileItemsCountVisibility => SelectedItems.Count > 0 ? Visibility.Visible : Visibility.Collapsed; public int SelectedFileItemsCount => SelectedItems.Count; @@ -274,7 +270,6 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { GoBackCommand = new SimpleCommand(GoBackAsync); GoForwardCommand = new SimpleCommand(GoForwardAsync); GoToUpperLevelCommand = new SimpleCommand(GoToUpperLevelAsync); - SelectionChangedCommand = new SimpleCommand(OnSelectionChanged); FileItemCommand = new FileItemCommand { TabControlProvider = () => OwnerTabControl, SelectedItemsProvider = () => SelectedItems @@ -303,10 +298,17 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { } if (param is CreateFileItem item) { try { - var fileName = item.Create(FullPath); - var newItem = AddSingleItem(fileName); - FileListView.ScrollIntoView(newItem); - StartRename(newItem); + var fileName = item.GetCreateName(FullPath); + var newName = OwnerWindow.StartRename(fileName); + if (newName != null) { + if (item.Create(FullPath, newName)) { + var newItem = AddSingleItem(newName); + newItem.IsSelected = true; + FileListView.ScrollIntoView(newItem); + } else { + hc.MessageBox.Error("", "CannotCreate".L()); + } + } } catch (Exception e) { hc.MessageBox.Error(e.Message, "CannotCreate".L()); } @@ -324,21 +326,19 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { } var fullPath = Path.Combine(FullPath, name); FileListViewItem item; - lock (Items) { - foreach (var fileListViewItem in Items) { - if (fileListViewItem.Name == name) { - return fileListViewItem; - } + foreach (var fileListViewItem in Items) { + if (fileListViewItem.Name == name) { + return fileListViewItem; } - if (File.Exists(fullPath)) { - item = new FileItem(new FileInfo(fullPath)); - } else if (Directory.Exists(fullPath)) { - item = new FolderItem(fullPath); - } else { - return null; - } - Items.Add(item); } + if (File.Exists(fullPath)) { + item = new FileItem(new FileInfo(fullPath)); + } else if (Directory.Exists(fullPath)) { + item = new FolderItem(fullPath); + } else { + return null; + } + Items.Add(item); UpdateFolderUI(); Task.Run(() => { item.LoadAttributes(loadDetailsOptions); @@ -362,7 +362,7 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { item.IsSelected = true; var originalName = item.GetRenameName(); var newName = OwnerWindow.StartRename(originalName); - if (newName != originalName) { + if (newName != null && newName != originalName) { item.Rename(newName); } } @@ -705,9 +705,7 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { FileView.CommitChange(); // 一旦调用这个,模板就会改变,所以要在清空之后,不然会导致排版混乱和绑定失败 if (fileListViewItems.Count > 0) { - foreach (var fileListViewItem in fileListViewItems) { - Items.Add(fileListViewItem); - } + Items.AddRange(fileListViewItems); _ = dispatcher.BeginInvoke(DispatcherPriority.Loaded, () => { GroupBy = savedView?.GroupBy; // Loaded之后再执行,不然会非常卡QAQ FileListView.ScrollIntoView(scrollIntoItem); @@ -795,7 +793,6 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { UpdateUI(nameof(GoForwardButtonToolTip)); UpdateUI(nameof(CanGoToUpperLevel)); UpdateUI(nameof(GoToUpperLevelButtonToolTip)); - UpdateUI(nameof(FileItemsCount)); UpdateUI(nameof(SearchPlaceholderText)); } @@ -859,8 +856,7 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { } } - private void OnSelectionChanged(object args) { - var e = (SelectionChangedEventArgs)args; + public void ChangeSelection(SelectionChangedEventArgs e) { foreach (FileListViewItem addedItem in e.AddedItems) { SelectedItems.Add(addedItem); addedItem.IsSelected = true; @@ -932,10 +928,7 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { return; } - Items.Clear(); - foreach (var fileListViewItem in fileListViewItems) { - Items.Add(fileListViewItem); - } + Items.Reset(fileListViewItems); UpdateFolderUI(); UpdateFileUI(); @@ -948,7 +941,7 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { } private void Watcher_OnRenamed(object sender, RenamedEventArgs e) { - dispatcher.BeginInvoke(() => { + Task.Run(() => { for (var i = 0; i < Items.Count; i++) { if (((FileSystemItem)Items[i]).FullPath == e.OldFullPath) { if (Directory.Exists(e.FullPath)) { @@ -973,9 +966,10 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { } private void Watcher_OnDeleted(object sender, FileSystemEventArgs e) { - dispatcher.BeginInvoke(() => { + Task.Run(() => { for (var i = 0; i < Items.Count; i++) { if (Items[i].FullPath == e.FullPath) { + SelectedItems.Remove(Items[i]); Items.RemoveAt(i); return; } @@ -984,7 +978,7 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { } private void Watcher_OnCreated(object sender, FileSystemEventArgs e) { - dispatcher.BeginInvoke(() => { + Task.Run(() => { if (Items.Any(i => i.Name == e.Name)) { return; } @@ -1005,16 +999,13 @@ public class FileTabViewModel : SimpleNotifyPropertyChanged, IDisposable { } private void Watcher_OnChanged(object sender, FileSystemEventArgs e) { - dispatcher.BeginInvoke(() => { - // ReSharper disable once PossibleInvalidCastExceptionInForeachLoop - Task.Run(() => { - foreach (FileSystemItem item in Items) { - if (item.FullPath == e.FullPath) { - item.Refresh(loadDetailsOptions); - return; - } + Task.Run(() => { + foreach (var item in Items.OfType()) { + if (item.FullPath == e.FullPath) { + item.Refresh(loadDetailsOptions); + return; } - }); + } }); } diff --git a/Readme.md b/Readme.md index 45ad06c..d47dfcb 100644 --- a/Readme.md +++ b/Readme.md @@ -69,7 +69,8 @@ Some time ago, when I was working on Minecraft mod, I needed to switch frequentl → fastcopy (Multi-thread copy) -### Thanks to -* HandyControl -* pinvoke.NET -* SvgToXaml +### Special Thanks to +* [HandyControl](https://github.com/HandyOrg/HandyControl) +* [pinvoke.net](https://www.pinvoke.net/) +* [SvgToXaml](https://github.com/BerndK/SvgToXaml) +* [Meziantou.Framework](https://github.com/meziantou/Meziantou.Framework)