using System.Collections.Specialized; using FluidExplorer.Helpers; using FluidExplorer.Models; using FluidExplorer.Navigation; using FluidExplorer.Services; using FluidExplorer.Services.Operations; using FluidExplorer.Services.Shell; using FluidExplorer.ViewModels; using Microsoft.UI.Input; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media; using Windows.ApplicationModel.DataTransfer; using Windows.System; using Windows.UI.Core; namespace FluidExplorer.Views; /// /// 单个浏览窗格的视图:命令栏 + 地址栏 + 内容区 + 状态栏。 /// 所有交互都委托给 ,视图本身不碰文件系统。 /// public sealed partial class ExplorerPaneView : UserControl { private static IReadOnlyList _draggedPaths = []; public ExplorerPaneView() { InitializeComponent(); Loaded += OnLoaded; Unloaded += OnUnloaded; } public static readonly DependencyProperty PaneProperty = DependencyProperty.Register( nameof(Pane), typeof(ExplorerPaneViewModel), typeof(ExplorerPaneView), new PropertyMetadata(null, OnPaneChanged)); public ExplorerPaneViewModel? Pane { get => (ExplorerPaneViewModel?)GetValue(PaneProperty); set => SetValue(PaneProperty, value); } public MainViewModel Main => App.Services.Main; private static void OnPaneChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var view = (ExplorerPaneView)d; if (e.OldValue is ExplorerPaneViewModel old) { old.PropertyChanged -= view.OnPanePropertyChanged; old.RenameRequested -= view.OnRenameRequested; old.ScrollIntoViewRequested -= view.OnScrollIntoViewRequested; old.FocusSearchRequested -= view.OnFocusSearchRequested; old.OpenInNewTabRequested -= view.OnOpenInNewTabRequested; } if (e.NewValue is ExplorerPaneViewModel pane) { pane.PropertyChanged += view.OnPanePropertyChanged; pane.RenameRequested += view.OnRenameRequested; pane.ScrollIntoViewRequested += view.OnScrollIntoViewRequested; pane.FocusSearchRequested += view.OnFocusSearchRequested; pane.OpenInNewTabRequested += view.OnOpenInNewTabRequested; view.ApplyIconSize(); } } private void OnLoaded(object sender, RoutedEventArgs e) { ApplyIconSize(); UpdateRecycleBinUi(); ApplySidebarLayout(); if (Pane is not null) SyncSidebarSelection(Pane.CurrentPath); } private void OnUnloaded(object sender, RoutedEventArgs e) { if (Pane is not null) Pane.PropertyChanged -= OnPanePropertyChanged; } private void OnPanePropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { switch (e.PropertyName) { case nameof(ExplorerPaneViewModel.IconSize): ApplyIconSize(); break; case nameof(ExplorerPaneViewModel.IsRecycleBin): UpdateRecycleBinUi(); break; case nameof(ExplorerPaneViewModel.CurrentPath): if (Pane is not null) SyncSidebarSelection(Pane.CurrentPath); break; case nameof(ExplorerPaneViewModel.ShowSidebar): ApplySidebarLayout(); break; } } private void UpdateRecycleBinUi() { RestoreButton.Visibility = Pane is { IsRecycleBin: true } ? Visibility.Visible : Visibility.Collapsed; HiddenToggle.IsChecked = Pane?.ShowHidden ?? false; ExtensionToggle.IsChecked = Main.ShowFileExtensions; } /// 图标/磁贴尺寸跟随视图方式(网格用 ItemsWrapGrid 的单元格尺寸,避免布局跳动)。 private void ApplyIconSize() { var pane = Pane; if (pane is null) return; foreach (var item in pane.Items) item.IconSize = Math.Min(pane.IconSize, 96); if (IconsGrid.ItemsPanelRoot is ItemsWrapGrid wrap) { wrap.ItemWidth = pane.TileWidth; wrap.ItemHeight = pane.TileHeight; } if (pane.IsIconsView && pane.Items.Count > 0) { // 视图切换后重新为可见项请求合适尺寸的缩略图 foreach (var item in pane.Items.Take(120)) pane.Visuals.RequestThumbnail(item, pane.IconSize); } } // ── 导航窗格(侧边栏) ────────────────────────────────────────────────── private bool _sidebarCollapsed; private void OnToggleSidebarClick(object sender, RoutedEventArgs e) { _sidebarCollapsed = !_sidebarCollapsed; ApplySidebarLayout(); } private void ApplySidebarLayout() { var show = Pane?.ShowSidebar != false && !_sidebarCollapsed; SidebarHost.Visibility = show ? Visibility.Visible : Visibility.Collapsed; SidebarColumn.Width = show ? new GridLength(242) : new GridLength(0); } private void OnSidebarItemInvoked(TreeView sender, TreeViewItemInvokedEventArgs args) { if (args.InvokedItem is SidebarNode node && Pane is not null) _ = Pane.NavigateAsync(node.Location); } private void OnSidebarExpanding(TreeView sender, TreeViewExpandingEventArgs args) { // 子项已在构建侧边栏时填充(驱动器数量少且读取廉价) } /// 当前路径变化时高亮侧边栏对应节点(对齐资源管理器)。 private void SyncSidebarSelection(string path) { if (string.IsNullOrEmpty(path)) return; foreach (var node in EnumerateNodes(SidebarTree.RootNodes)) { if (node.Content is SidebarNode sidebarNode && sidebarNode.Contains(path)) { SidebarTree.SelectedNode = node; return; } } } private static IEnumerable EnumerateNodes(IEnumerable nodes) { foreach (var node in nodes) { yield return node; foreach (var child in EnumerateNodes(node.Children)) yield return child; } } // ── 地址栏导航按钮 ────────────────────────────────────────────────────── private void OnBackClick(object sender, RoutedEventArgs e) => Pane?.BackCommand.Execute(null); private void OnForwardClick(object sender, RoutedEventArgs e) => Pane?.ForwardCommand.Execute(null); private void OnUpClick(object sender, RoutedEventArgs e) => Pane?.UpCommand.Execute(null); private void OnRefreshClick(object sender, RoutedEventArgs e) => Pane?.RefreshCommand.Execute(null); // ── 命令栏 ────────────────────────────────────────────────────────────── private void OnNewFolderClick(object sender, RoutedEventArgs e) => Pane?.NewFolderCommand.Execute(null); private void OnNewTextFileClick(object sender, RoutedEventArgs e) { if (Pane is null || Pane.Location.Kind is not (LocationKind.Folder or LocationKind.Drive)) return; try { var path = Path.Combine(Pane.CurrentPath, "新建文本文档.txt"); var i = 2; while (File.Exists(path)) path = Path.Combine(Pane.CurrentPath, $"新建文本文档 ({i++}).txt"); File.WriteAllText(path, string.Empty); _ = Pane.RefreshCommand.ExecuteAsync(null); } catch (Exception ex) { Pane.ReportError($"无法新建文件:{ex.Message}"); } } private void OnNewTabClick(object sender, RoutedEventArgs e) => Main.AddTab(NavigationLocation.FromPath(Pane?.CurrentPath ?? string.Empty)); private void OnCutClick(object sender, RoutedEventArgs e) => Pane?.CutSelectedCommand.Execute(SelectedItems()); private void OnCopyClick(object sender, RoutedEventArgs e) => Pane?.CopySelectedCommand.Execute(SelectedItems()); private void OnPasteClick(object sender, RoutedEventArgs e) => Pane?.PasteCommand.Execute(null); private void OnRenameClick(object sender, RoutedEventArgs e) { var item = SelectedItems().FirstOrDefault(); if (item is not null) Pane?.StartRenameCommand.Execute(item); } private void OnDeleteClick(object sender, RoutedEventArgs e) { var items = SelectedItems(); if (items.Count == 0) return; var shift = IsKeyDown(VirtualKey.Shift); if (shift) Pane?.PermanentDeleteSelectedCommand.Execute(items); else Pane?.DeleteSelectedCommand.Execute(items); } private void OnUndoClick(object sender, RoutedEventArgs e) => Main.UndoCommand.Execute(null); private void OnRestoreClick(object sender, RoutedEventArgs e) => Pane?.RestoreSelectedCommand.Execute(SelectedItems()); private void OnSortClick(object sender, RoutedEventArgs e) { if (sender is FrameworkElement { Tag: string tag } && Enum.TryParse(tag, out var column)) Pane?.SortByCommand.Execute(column); } private void OnSortDirectionClick(object sender, RoutedEventArgs e) { if (Pane is null || sender is not FrameworkElement { Tag: string tag }) return; Pane.Sort = Pane.Sort with { Direction = tag == "Descending" ? SortDirection.Descending : SortDirection.Ascending }; Pane.ApplySortAndRefresh(); } private void OnViewModeClick(object sender, RoutedEventArgs e) { if (sender is FrameworkElement { Tag: string tag }) Pane?.SetViewModeCommand.Execute(tag); } private void OnCycleViewClick(object sender, RoutedEventArgs e) => Pane?.CycleViewModeCommand.Execute(null); private void OnToggleDualPaneClick(object sender, RoutedEventArgs e) => Main.ToggleDualPaneCommand.Execute(null); private void OnToggleHiddenClick(object sender, RoutedEventArgs e) => Pane?.ToggleHiddenCommand.Execute(null); private void OnToggleExtensionClick(object sender, RoutedEventArgs e) => Main.ShowFileExtensions = !Main.ShowFileExtensions; private async void OnShellContextMenuClick(object sender, RoutedEventArgs e) { var paths = SelectedItems().Select(i => i.FullPath).ToList(); if (paths.Count == 0) { var dir = Pane?.CurrentPath; if (!string.IsNullOrEmpty(dir) && Directory.Exists(dir)) paths.Add(dir); } if (paths.Count > 0) await ShowShellContextMenuAsync(paths); } private async Task ShowShellContextMenuAsync(IReadOnlyList paths) { if (paths.Count == 0) return; var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.MainWindowInstance); var anchor = DetailsList.TransformToVisual(null).TransformPoint(new Windows.Foundation.Point(48, 48)); var scale = XamlRoot?.RasterizationScale ?? 1.0; var origin = App.MainWindowInstance.AppWindow.Position; var screen = new Windows.Graphics.PointInt32( origin.X + (int)(anchor.X * scale), origin.Y + (int)(anchor.Y * scale)); await ShellContextMenu.ShowAsync(hwnd, paths, screen); } // ── 地址栏 ────────────────────────────────────────────────────────────── private async void OnBreadcrumbClicked(BreadcrumbBar sender, BreadcrumbBarItemClickedEventArgs args) { if (args.Item is BreadcrumbSegment segment && Pane is not null) await Pane.NavigateAsync(new NavigationLocation( segment.IsDriveRoot ? LocationKind.Drive : LocationKind.Folder, segment.Path, segment.DisplayName, "\uE8B7")); } private void OnAddressEditorKeyDown(object sender, KeyRoutedEventArgs e) { if (e.Key == VirtualKey.Enter) { e.Handled = true; var text = AddressEditor.Text; AddressEditor.Visibility = Visibility.Collapsed; AddressBreadcrumb.Visibility = Visibility.Visible; _ = Pane?.NavigatePathCommand.ExecuteAsync(text); } else if (e.Key == VirtualKey.Escape) { e.Handled = true; AddressEditor.Visibility = Visibility.Collapsed; AddressBreadcrumb.Visibility = Visibility.Visible; } } private void OnAddressEditorLostFocus(object sender, RoutedEventArgs e) { AddressEditor.Visibility = Visibility.Collapsed; AddressBreadcrumb.Visibility = Visibility.Visible; } /// Ctrl+L / F4:切换到可编辑的地址输入(资源管理器行为)。 private void BeginAddressEdit() { AddressEditor.Text = Pane?.CurrentPath ?? string.Empty; AddressEditor.Visibility = Visibility.Visible; AddressBreadcrumb.Visibility = Visibility.Collapsed; AddressEditor.Focus(FocusState.Programmatic); AddressEditor.SelectAll(); } // ── 搜索 ──────────────────────────────────────────────────────────────── private async void OnSearchTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) { // SuggestionChosen 是我们自己回填的,不重复搜索;其余变化(含程序化写入、粘贴)都要触发 if (args.Reason == AutoSuggestionBoxTextChangeReason.SuggestionChosen) return; if (Pane is null) return; if (!Main.Settings.SearchAsYouType && args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) { // 关闭"键入即搜"时只在回车/提交时搜索 return; } await Pane.SearchAsync(sender.Text); // 顶部建议:直接把最快命中的前 8 条塞进下拉框(Everything 式即时反馈) var suggestions = Pane.SearchResults.Take(8).Select(r => r.FullPath).ToList(); sender.ItemsSource = suggestions; } private async void OnSearchQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { if (Pane is null) return; await Pane.SearchAsync(args.QueryText); } private async void OnSearchSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) { if (Pane is null || args.SelectedItem is not string path) return; sender.Text = path; await Pane.SearchAsync(path); } private void OnDismissErrorClick(InfoBar sender, object args) => Pane?.DismissError(); // ── 列表交互 ──────────────────────────────────────────────────────────── private List SelectedItems() => Pane is null ? [] : (Pane.IsDetailsView || Pane.IsIconsView ? (Pane.IsDetailsView ? DetailsList.SelectedItems : IconsGrid.SelectedItems).OfType().ToList() : []); private void OnListSelectionChanged(object sender, SelectionChangedEventArgs e) { if (Pane is null) return; var listView = sender as ListViewBase; var selected = listView?.SelectedItems.OfType().ToList() ?? []; Pane.OnSelectionChanged(selected); } private void OnDetailsContainerChanging(ListViewBase sender, ContainerContentChangingEventArgs args) { if (args.InRecycleQueue || args.Item is not ExplorerItem item) return; Pane?.Visuals.RequestIcon(item, 16); } private void OnIconsContainerChanging(ListViewBase sender, ContainerContentChangingEventArgs args) { if (args.InRecycleQueue || args.Item is not ExplorerItem item || Pane is null) return; item.IconSize = Math.Min(Pane.IconSize, 96); Pane.Visuals.RequestThumbnail(item, Pane.IconSize); } private void OnSearchContainerChanging(ListViewBase sender, ContainerContentChangingEventArgs args) { if (args.InRecycleQueue || args.Item is not SearchResultItem item) return; Pane?.Visuals.RequestIcon(item, 16); } private void OnItemDoubleTapped(object sender, DoubleTappedRoutedEventArgs e) { if (Pane is null) return; var item = ResolveItemAt(e.OriginalSource as DependencyObject); if (item is not null) _ = Pane.OpenItemAsync(item); } private void OnSearchResultDoubleTapped(object sender, DoubleTappedRoutedEventArgs e) { var item = ResolveSearchItemAt(e.OriginalSource as DependencyObject); if (item is not null) _ = Pane!.OpenSearchResultAsync(item); } private static ExplorerItem? ResolveItemAt(DependencyObject? source) { while (source is not null) { if (source is FrameworkElement { DataContext: ExplorerItem item }) return item; source = VisualTreeHelper.GetParent(source); } return null; } private static SearchResultItem? ResolveSearchItemAt(DependencyObject? source) { while (source is not null) { if (source is FrameworkElement { DataContext: SearchResultItem item }) return item; source = VisualTreeHelper.GetParent(source); } return null; } private void OnListKeyDown(object sender, KeyRoutedEventArgs e) { if (Pane is null) return; var ctrl = IsKeyDown(VirtualKey.Control); var shift = IsKeyDown(VirtualKey.Shift); switch (e.Key) { case VirtualKey.Enter when !ctrl: e.Handled = true; Pane.OpenSelectedCommand.Execute(SelectedItems()); break; case VirtualKey.F2: e.Handled = true; Pane.StartRenameCommand.Execute(SelectedItems().FirstOrDefault()); break; case VirtualKey.Back when !ctrl: e.Handled = true; Pane.UpCommand.Execute(null); break; case VirtualKey.Delete: e.Handled = true; OnDeleteClick(sender, e); break; case VirtualKey.F5: e.Handled = true; Pane.RefreshCommand.Execute(null); break; case VirtualKey.C when ctrl && shift: e.Handled = true; Pane.CopyPathSelectedCommand.Execute(SelectedItems()); break; case VirtualKey.C when ctrl: e.Handled = true; Pane.CopySelectedCommand.Execute(SelectedItems()); break; case VirtualKey.X when ctrl: e.Handled = true; Pane.CutSelectedCommand.Execute(SelectedItems()); break; case VirtualKey.V when ctrl: e.Handled = true; Pane.PasteCommand.Execute(null); break; case VirtualKey.N when ctrl && shift: e.Handled = true; Pane.NewFolderCommand.Execute(null); break; case VirtualKey.L when ctrl: e.Handled = true; BeginAddressEdit(); break; case VirtualKey.Up when IsKeyDown(VirtualKey.Menu): e.Handled = true; Pane.UpCommand.Execute(null); break; case VirtualKey.Left when IsKeyDown(VirtualKey.Menu): e.Handled = true; Pane.BackCommand.Execute(null); break; case VirtualKey.Right when IsKeyDown(VirtualKey.Menu): e.Handled = true; Pane.ForwardCommand.Execute(null); break; } } private static bool IsKeyDown(VirtualKey key) => InputKeyboardSource.GetKeyStateForCurrentThread(key).HasFlag(CoreVirtualKeyStates.Down); // ── 重命名输入框 ──────────────────────────────────────────────────────── private void OnRenameRequested(object? sender, ExplorerItem item) { DispatcherQueue.TryEnqueue(() => FocusRenameBox(item)); } private void FocusRenameBox(ExplorerItem item) { var host = Pane?.IsDetailsView == true ? (DependencyObject)DetailsList : IconsGrid; var container = host is ListViewBase list ? list.ContainerFromItem(item) as DependencyObject : null; if (container is null) return; if (FindRenameBox(container) is { } box) { box.Focus(FocusState.Programmatic); box.SelectAll(); } } private static TextBox? FindRenameBox(DependencyObject root) { if (root is TextBox { Tag: "rename" } box) return box; var count = VisualTreeHelper.GetChildrenCount(root); for (var i = 0; i < count; i++) { var child = VisualTreeHelper.GetChild(root, i); if (FindRenameBox(child) is { } found) return found; } return null; } private void OnRenameBoxKeyDown(object sender, KeyRoutedEventArgs e) { if (sender is not TextBox box || box.DataContext is not ExplorerItem item) return; if (e.Key == VirtualKey.Enter) { e.Handled = true; Pane?.CommitRenameCommand.Execute(item); } else if (e.Key == VirtualKey.Escape) { e.Handled = true; Pane?.CancelRenameCommand.Execute(item); } } private void OnRenameBoxLostFocus(object sender, RoutedEventArgs e) { if (sender is TextBox { DataContext: ExplorerItem item } && item.IsRenaming) Pane?.CommitRenameCommand.Execute(item); } private void OnScrollIntoViewRequested(object? sender, ExplorerItem item) { if (Pane?.IsDetailsView == true) DetailsList.ScrollIntoView(item); else IconsGrid.ScrollIntoView(item); } private void OnFocusSearchRequested(object? sender, EventArgs e) { SearchBox.Focus(FocusState.Programmatic); } private void OnOpenInNewTabRequested(object? sender, NavigationLocation location) { Main.AddTab(location); } // ── 右键菜单(应用内 + 系统原版) ─────────────────────────────────────── private void OnContextRequested(UIElement sender, ContextRequestedEventArgs args) { if (Pane is null || sender is not FrameworkElement target) return; var selected = SelectedItems(); var flyout = BuildContextFlyout(selected); if (args.TryGetPosition(sender, out var position)) flyout.ShowAt(target, new FlyoutShowOptions { Position = position }); else flyout.ShowAt(target); args.Handled = true; } private void OnSearchResultContextRequested(UIElement sender, ContextRequestedEventArgs args) { if (sender is not FrameworkElement target) return; var flyout = new MenuFlyout(); var openItem = new MenuFlyoutItem { Text = "打开" }; openItem.Click += (_, _) => _ = Pane?.OpenSearchResultCommand.ExecuteAsync(SingleSearchResult(sender)); var openFolder = new MenuFlyoutItem { Text = "打开所在文件夹" }; openFolder.Click += (_, _) => _ = Pane?.OpenSearchResultFolderCommand.ExecuteAsync(SingleSearchResult(sender)); var copyPath = new MenuFlyoutItem { Text = "复制完整路径" }; copyPath.Click += (_, _) => { var item = SingleSearchResult(sender); if (item is null) return; var package = new DataPackage(); package.SetText(item.FullPath); Clipboard.SetContent(package); }; flyout.Items.Add(openItem); flyout.Items.Add(openFolder); flyout.Items.Add(copyPath); if (args.TryGetPosition(sender, out var position)) flyout.ShowAt(target, new FlyoutShowOptions { Position = position }); else flyout.ShowAt(target); args.Handled = true; SearchResultItem? SingleSearchResult(UIElement element) => (element as ListViewBase)?.SelectedItems.OfType().FirstOrDefault(); } private MenuFlyout BuildContextFlyout(IReadOnlyList selection) { var flyout = new MenuFlyout(); if (selection.Count > 0) { var open = new MenuFlyoutItem { Text = "打开", Icon = new FontIcon { Glyph = "\uE8E5" } }; open.Click += (_, _) => Pane?.OpenSelectedCommand.Execute(selection); flyout.Items.Add(open); if (selection.Count == 1 && !selection[0].IsDirectory) { var openWith = new MenuFlyoutItem { Text = "打开方式" }; openWith.Click += (_, _) => Pane?.OpenWithSelectedCommand.Execute(selection); flyout.Items.Add(openWith); } if (selection.Count == 1 && selection[0].IsDirectory) { var newTab = new MenuFlyoutItem { Text = "在新标签页中打开" }; newTab.Click += (_, _) => Pane?.OpenInNewTabCommand.Execute(selection[0]); flyout.Items.Add(newTab); } flyout.Items.Add(new MenuFlyoutSeparator()); var cut = new MenuFlyoutItem { Text = "剪切", Icon = new FontIcon { Glyph = "\uE8C6" } }; cut.Click += (_, _) => Pane?.CutSelectedCommand.Execute(selection); var copy = new MenuFlyoutItem { Text = "复制", Icon = new FontIcon { Glyph = "\uE8C8" } }; copy.Click += (_, _) => Pane?.CopySelectedCommand.Execute(selection); var copyPath = new MenuFlyoutItem { Text = "复制完整路径" }; copyPath.Click += (_, _) => Pane?.CopyPathSelectedCommand.Execute(selection); var rename = new MenuFlyoutItem { Text = "重命名", Icon = new FontIcon { Glyph = "\uE8AC" } }; rename.Click += (_, _) => Pane?.StartRenameCommand.Execute(selection[0]); var delete = new MenuFlyoutItem { Text = "删除", Icon = new FontIcon { Glyph = "\uE74D" } }; delete.Click += (_, _) => Pane?.DeleteSelectedCommand.Execute(selection); flyout.Items.Add(cut); flyout.Items.Add(copy); flyout.Items.Add(copyPath); flyout.Items.Add(rename); flyout.Items.Add(delete); if (Pane is { IsRecycleBin: true }) { var restore = new MenuFlyoutItem { Text = "还原" }; restore.Click += (_, _) => Pane?.RestoreSelectedCommand.Execute(selection); flyout.Items.Add(restore); } flyout.Items.Add(new MenuFlyoutSeparator()); var properties = new MenuFlyoutItem { Text = "属性", Icon = new FontIcon { Glyph = "\uE946" } }; properties.Click += (_, _) => Pane?.ShowPropertiesSelectedCommand.Execute(selection); var reveal = new MenuFlyoutItem { Text = "在资源管理器中显示" }; reveal.Click += (_, _) => Pane?.RevealSelectedCommand.Execute(selection); flyout.Items.Add(properties); flyout.Items.Add(reveal); } else { var refresh = new MenuFlyoutItem { Text = "刷新", Icon = new FontIcon { Glyph = "\uE72C" } }; refresh.Click += (_, _) => Pane?.RefreshCommand.Execute(null); var newFolder = new MenuFlyoutItem { Text = "新建文件夹", Icon = new FontIcon { Glyph = "\uE8B7" } }; newFolder.Click += (_, _) => Pane?.NewFolderCommand.Execute(null); var paste = new MenuFlyoutItem { Text = "粘贴", Icon = new FontIcon { Glyph = "\uE77F" } }; paste.Click += (_, _) => Pane?.PasteCommand.Execute(null); flyout.Items.Add(refresh); flyout.Items.Add(newFolder); flyout.Items.Add(paste); } flyout.Items.Add(new MenuFlyoutSeparator()); var showMore = new MenuFlyoutItem { Text = "显示更多选项(系统菜单)" }; showMore.Click += async (_, _) => await ShowShellContextMenuAsync( selection.Count > 0 ? selection.Select(i => i.FullPath).ToList() : (Pane?.CurrentPath is { Length: > 0 } p && Directory.Exists(p) ? [p] : [])); flyout.Items.Add(showMore); return flyout; } // ── 拖放 ──────────────────────────────────────────────────────────────── private void OnDragItemsStarting(object sender, DragItemsStartingEventArgs e) { var items = e.Items.OfType().ToList(); var paths = items.Select(i => i.FullPath).ToList(); _draggedPaths = paths; e.Data.RequestedOperation = DataPackageOperation.Copy | DataPackageOperation.Move; e.Data.SetDataProvider(StandardDataFormats.StorageItems, async request => { var deferral = request.GetDeferral(); try { var storageItems = new List(); foreach (var path in paths) { try { if (Directory.Exists(path)) storageItems.Add(await Windows.Storage.StorageFolder.GetFolderFromPathAsync(path)); else if (File.Exists(path)) storageItems.Add(await Windows.Storage.StorageFile.GetFileFromPathAsync(path)); } catch { // 单个项目不可用时跳过,不阻断整次拖放 } } request.SetData(storageItems); } finally { deferral.Complete(); } }); } private void OnDragOver(object sender, DragEventArgs e) { if (!e.DataView.Contains(StandardDataFormats.StorageItems)) return; var copy = IsKeyDown(VirtualKey.Control); e.AcceptedOperation = copy ? DataPackageOperation.Copy : DataPackageOperation.Move; e.DragUIOverride.Caption = copy ? $"复制到“{Pane?.Title}”" : $"移动到“{Pane?.Title}”"; e.DragUIOverride.IsCaptionVisible = true; } private async void OnDrop(object sender, DragEventArgs e) { if (Pane is null || !e.DataView.Contains(StandardDataFormats.StorageItems)) return; var deferral = e.GetDeferral(); try { var target = ResolveDropTarget(e); var pathCopy = IsKeyDown(VirtualKey.Control); // 优先使用应用内拖动的路径(避免 StorageItem 往返),否则从数据包读取 var paths = _draggedPaths.Count > 0 ? _draggedPaths.ToList() : (await e.DataView.GetStorageItemsAsync()).Select(i => i.Path).ToList(); await Pane.DropIntoAsync(paths, target, pathCopy); } catch (Exception ex) { Pane.ReportError($"拖放失败:{ex.Message}"); } finally { deferral.Complete(); } } /// 拖到文件夹行上 → 放进该文件夹;拖到空白处 → 放进当前文件夹。 private string ResolveDropTarget(DragEventArgs e) { var dropPoint = e.GetPosition(DetailsList); var elements = VisualTreeHelper.FindElementsInHostCoordinates(dropPoint, DetailsList); foreach (var element in elements) { if (element is FrameworkElement { DataContext: ExplorerItem { IsDirectory: true } folder }) return folder.FullPath; } return Pane?.CurrentPath ?? string.Empty; } // ── 状态栏 ────────────────────────────────────────────────────────────── private void OnIndexChipTapped(object sender, TappedRoutedEventArgs e) { if (Main.IndexNeedsElevation) Main.RestartElevatedCommand.Execute(null); else Main.BuildIndexCommand.Execute(null); } private void OnOpenQueueClick(object sender, RoutedEventArgs e) { Main.IsQueueExpanded = true; App.MainWindowInstance.ShowOperationQueue(); } private async void OnOpenSettingsClick(object sender, RoutedEventArgs e) { var dialog = new SettingsDialog(Main) { XamlRoot = XamlRoot }; await dialog.ShowAsync(); } }