1027 lines
37 KiB
C#
1027 lines
37 KiB
C#
using HeBianGu.Base.WpfBase;
|
||
using HeBianGu.Control.Explorer;
|
||
using Hopetry.Services;
|
||
using Minio.DataModel.Args;
|
||
using Minio;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using Microsoft.Extensions.Configuration;
|
||
using Minio.ApiEndpoints;
|
||
using System.Windows.Threading;
|
||
using System.Drawing;
|
||
using System.Windows.Media.Imaging;
|
||
using System.Windows.Media;
|
||
using System.Windows.Controls;
|
||
using System.Collections.ObjectModel;
|
||
using HeBianGu.App.Disk;
|
||
using System.Windows.Controls.Primitives;
|
||
using System.Diagnostics;
|
||
using System.Windows.Input;
|
||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||
using System.Reflection;
|
||
using Minio.DataModel.Select;
|
||
|
||
namespace Hopetry.Provider.Behaviors
|
||
{
|
||
public class ExplorerMinIOBehavior : Behavior<Explorer>
|
||
{
|
||
#region 依赖属性
|
||
|
||
public bool UseMinIO
|
||
{
|
||
get { return (bool)GetValue(UseMinIOProperty); }
|
||
set { SetValue(UseMinIOProperty, value); }
|
||
}
|
||
|
||
public static readonly DependencyProperty UseMinIOProperty =
|
||
DependencyProperty.Register("UseMinIO", typeof(bool), typeof(ExplorerMinIOBehavior),
|
||
new PropertyMetadata(false, OnUseMinIOChanged));
|
||
|
||
public static readonly DependencyProperty StatusMessageProperty =
|
||
DependencyProperty.Register(
|
||
"StatusMessage",
|
||
typeof(string),
|
||
typeof(ExplorerMinIOBehavior),
|
||
new PropertyMetadata(null));
|
||
|
||
public string StatusMessage
|
||
{
|
||
get => (string)GetValue(StatusMessageProperty);
|
||
set => SetValue(StatusMessageProperty, value);
|
||
}
|
||
|
||
//获取当前选中的文件
|
||
public IEnumerable<SystemInfoModel> GetSelectedItems()
|
||
{
|
||
var dataGrid = FindVisualChild<DataGrid>(AssociatedObject);
|
||
if (dataGrid != null)
|
||
{
|
||
return dataGrid.SelectedItems.Cast<SystemInfoModel>().ToList();
|
||
}
|
||
return Enumerable.Empty<SystemInfoModel>();
|
||
}
|
||
#endregion
|
||
|
||
#region 字段和初始化
|
||
private IMinioClient _minioClient;
|
||
private bool _isLocalHistoryRefresh;
|
||
private bool _isNavigationCommand = false;
|
||
protected override void OnAttached()
|
||
{
|
||
base.OnAttached();
|
||
|
||
InitializeMinIOClient();
|
||
|
||
// 监听路径变化
|
||
var pathDescriptor = DependencyPropertyDescriptor.FromProperty(
|
||
Explorer.CurrentPathProperty, typeof(Explorer));
|
||
pathDescriptor.AddValueChanged(AssociatedObject, OnCurrentPathChanged);
|
||
|
||
// 监听历史记录变化
|
||
var historyDescriptor = DependencyPropertyDescriptor.FromProperty(
|
||
Explorer.HistoryProperty, typeof(Explorer));
|
||
historyDescriptor.AddValueChanged(AssociatedObject, OnHistoryChanged);
|
||
|
||
// 初始加载MinIO内容
|
||
if (UseMinIO)
|
||
{
|
||
Dispatcher.BeginInvoke((Action)(() =>
|
||
{
|
||
RefreshMinIOPath(AssociatedObject.CurrentPath);
|
||
}), DispatcherPriority.Loaded);
|
||
}
|
||
|
||
// 延迟执行以确保UI元素已加载
|
||
Dispatcher.BeginInvoke(new Action(() =>
|
||
{
|
||
try
|
||
{
|
||
// 更安全的方式查找DataGrid
|
||
var dataGrid = FindVisualChild<DataGrid>(AssociatedObject);
|
||
if (dataGrid != null)
|
||
{
|
||
dataGrid.CellEditEnding += DataGrid_CellEditEnding;
|
||
}
|
||
else
|
||
{
|
||
// 如果首次查找失败,可以尝试延迟再次查找
|
||
Dispatcher.BeginInvoke(new Action(() =>
|
||
{
|
||
dataGrid = FindVisualChild<DataGrid>(AssociatedObject);
|
||
if (dataGrid != null)
|
||
{
|
||
dataGrid.CellEditEnding += DataGrid_CellEditEnding;
|
||
}
|
||
}), DispatcherPriority.Loaded);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 记录错误但不中断程序
|
||
System.Diagnostics.Debug.WriteLine($"附加CellEditEnding事件失败: {ex.Message}");
|
||
}
|
||
}), DispatcherPriority.Loaded);
|
||
|
||
|
||
var dataGrid = FindVisualChild<DataGrid>(AssociatedObject);
|
||
if (dataGrid != null)
|
||
{
|
||
dataGrid.BeginningEdit += DataGrid_BeginningEdit;
|
||
}
|
||
AssociatedObject.Loaded += OnExplorerLoaded;
|
||
// 监听导航命令
|
||
CommandManager.AddPreviewExecutedHandler(AssociatedObject, OnPreviewCommandExecuted);
|
||
}
|
||
|
||
protected override void OnDetaching()
|
||
{
|
||
// Clean up event handlers
|
||
var pathDescriptor = DependencyPropertyDescriptor.FromProperty(
|
||
Explorer.CurrentPathProperty, typeof(Explorer));
|
||
pathDescriptor.RemoveValueChanged(AssociatedObject, OnCurrentPathChanged);
|
||
|
||
var historyDescriptor = DependencyPropertyDescriptor.FromProperty(
|
||
Explorer.HistoryProperty, typeof(Explorer));
|
||
historyDescriptor.RemoveValueChanged(AssociatedObject, OnHistoryChanged);
|
||
|
||
var dataGrid = FindVisualChild<DataGrid>(AssociatedObject);
|
||
if (dataGrid != null)
|
||
{
|
||
dataGrid.CellEditEnding -= DataGrid_CellEditEnding;
|
||
}
|
||
CommandManager.RemovePreviewExecutedHandler(AssociatedObject, OnPreviewCommandExecuted);
|
||
|
||
base.OnDetaching();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 事件处理
|
||
|
||
private static void OnUseMinIOChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||
{
|
||
var behavior = (ExplorerMinIOBehavior)d;
|
||
if ((bool)e.NewValue && behavior.AssociatedObject != null)
|
||
{
|
||
behavior.Dispatcher.BeginInvoke((Action)(() =>
|
||
{
|
||
behavior.RefreshMinIOPath(behavior.AssociatedObject.CurrentPath);
|
||
}), DispatcherPriority.ContextIdle);
|
||
}
|
||
}
|
||
private void OnPreviewCommandExecuted(object sender, ExecutedRoutedEventArgs e)
|
||
{
|
||
if (e.Command == Explorer.Previous || e.Command == Explorer.Next)
|
||
{
|
||
_isNavigationCommand = true;
|
||
Dispatcher.BeginInvoke(new Action(() => _isNavigationCommand = false),
|
||
DispatcherPriority.ApplicationIdle);
|
||
}
|
||
}
|
||
private void OnCurrentPathChanged(object sender, EventArgs e)
|
||
{
|
||
if (UseMinIO)
|
||
{
|
||
RefreshMinIOPath(AssociatedObject.CurrentPath);
|
||
}
|
||
}
|
||
|
||
private void OnHistoryChanged(object sender, EventArgs e)
|
||
{
|
||
// 只有当历史记录变更不是由导航触发时才标记为本地刷新
|
||
_isLocalHistoryRefresh = true;
|
||
}
|
||
//定义状态更新事件
|
||
public event EventHandler<string> StatusMessageChanged;
|
||
|
||
#endregion
|
||
|
||
#region MinIO 操作
|
||
|
||
private void InitializeMinIOClient()
|
||
{
|
||
var builder = new ConfigurationBuilder()
|
||
.SetBasePath(Directory.GetCurrentDirectory())
|
||
.AddJsonFile("global.json", optional: false, reloadOnChange: true);
|
||
// 构建配置
|
||
var config = builder.Build();
|
||
|
||
_minioClient = new MinioClient()
|
||
.WithEndpoint(config["Minio:Endpoint"])
|
||
.WithCredentials(config["Minio:AccessKey"], config["Minio:SecretKey"])
|
||
.Build();
|
||
}
|
||
|
||
public async void RefreshMinIOPath(string path, string searchText = "")
|
||
{
|
||
if (AssociatedObject == null || _minioClient == null) return;
|
||
|
||
try
|
||
{
|
||
// 获取绑定的 ViewModel
|
||
if (AssociatedObject.DataContext is LoyoutViewModel vm)
|
||
{
|
||
// 更新 ViewModel 状态(自动通过属性变更通知更新UI)
|
||
vm.SelectedItems.Clear();
|
||
}
|
||
AssociatedObject.IsEnabled = false;
|
||
AssociatedObject.Cursor = System.Windows.Input.Cursors.Wait;
|
||
|
||
var items = await GetMinIOItemsAsync(path, searchText);
|
||
var filecount=items.Count();
|
||
StatusMessage = $"已全部加载,共 {filecount} 个文件";
|
||
AssociatedObject.ItemsSource = items.ToObservable();
|
||
|
||
//var items = await GetMinIOItemsAsync(path);
|
||
//AssociatedObject.ItemsSource = items.ToObservable();
|
||
|
||
// 2. 刷新导航栏
|
||
var navigationBar = FindVisualChild<NavigationBar>(AssociatedObject);
|
||
if (navigationBar != null)
|
||
{
|
||
RefreshNavigationBar(navigationBar, path);
|
||
}
|
||
|
||
// Update history if this wasn't triggered by a history navigation
|
||
if (!_isNavigationCommand && !string.IsNullOrEmpty(path))
|
||
{
|
||
UpdateHistory(path);
|
||
}
|
||
|
||
_isLocalHistoryRefresh = false;
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
MessageBox.Show($"Error accessing MinIO: {ex.Message}", "Error",
|
||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
finally
|
||
{
|
||
AssociatedObject.IsEnabled = true;
|
||
AssociatedObject.Cursor = null;
|
||
}
|
||
}
|
||
|
||
private async Task<IEnumerable<SystemInfoModel>> GetMinIOItemsAsync(string path, string searchText)
|
||
{
|
||
var items = new List<SystemInfoModel>();
|
||
|
||
if (string.IsNullOrEmpty(path) || path == "全部文件")
|
||
{
|
||
// List all buckets
|
||
var buckets = await _minioClient.ListBucketsAsync();
|
||
items.AddRange(buckets.Buckets.Select(b =>
|
||
new MinIODirectoryModel(new MinIODirectoryInfo(b.Name, null, true, b.CreationDateDateTime))));
|
||
}
|
||
else
|
||
{
|
||
// List objects in a bucket/path
|
||
var parts = path.Split(new[] { '/' }, 2);
|
||
var bucketName = parts[0];
|
||
var prefix = parts.Length > 1 ? parts[1] : null;
|
||
|
||
var args = new ListObjectsArgs()
|
||
.WithBucket(bucketName)
|
||
.WithPrefix(prefix ?? "")
|
||
.WithRecursive(false);
|
||
|
||
var observable = _minioClient.ListObjectsEnumAsync(args);
|
||
|
||
await foreach (var item in observable)
|
||
{
|
||
// 解码为正确的字符串
|
||
var decodedKey = Uri.UnescapeDataString(item.Key);
|
||
if (item.IsDir)
|
||
{
|
||
items.Add(new MinIODirectoryModel(
|
||
new MinIODirectoryInfo(bucketName, decodedKey, false, DateTime.Now)));
|
||
}
|
||
else
|
||
{
|
||
if (!string.IsNullOrEmpty(Path.GetFileName(decodedKey)))
|
||
{
|
||
string size = item.Size < 1024 * 1024 ?
|
||
$"{Math.Ceiling((decimal)item.Size / 1024)}KB" :
|
||
$"{Math.Ceiling((decimal)item.Size / (1024 * 1024))}MB";
|
||
items.Add(new MinIOFileModel(new MinIOFileInfo(bucketName, decodedKey, size, item.LastModifiedDateTime)));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 按修改时间降序排序(最新修改的排前面)
|
||
return items.Where(x => x.DisplayName.Contains(searchText)).OrderByDescending(x =>
|
||
{
|
||
if (x is MinIOFileModel fileModel)
|
||
{
|
||
return ((MinIOFileInfo)fileModel.Model).LastModified;
|
||
}
|
||
return DateTime.Now; // 文件夹使用当前时间(或根据需求调整)
|
||
}).ToList();
|
||
}
|
||
|
||
//private void UpdateHistory(string path)
|
||
//{
|
||
// if (string.IsNullOrEmpty(path))
|
||
// {
|
||
// // 根路径时创建一个特殊的目录模型
|
||
// var rootItem = new DirectoryModel(new MinIODirectoryInfo("", "", true, DateTime.Now))
|
||
// {
|
||
// DisplayName = RootDisplayName
|
||
// };
|
||
// AssociatedObject.History.Insert(0, rootItem);
|
||
// }
|
||
// else
|
||
// {
|
||
// var parts = path.Split(new[] { '/' }, 2);
|
||
// var bucketName = parts[0];
|
||
// var prefix = parts.Length > 1 ? parts[1] : null;
|
||
|
||
// var dirInfo = new MinIODirectoryInfo(bucketName, prefix, false, DateTime.Now);
|
||
|
||
// var historyItem = new DirectoryModel(dirInfo);
|
||
|
||
// if (AssociatedObject.History.FirstOrDefault()?.Model?.FullName != path)
|
||
// {
|
||
// AssociatedObject.History.Insert(0, historyItem);
|
||
// AssociatedObject.History = AssociatedObject.History
|
||
// .Take(ExplorerSetting.Instance.HistCapacity)
|
||
// .ToObservable();
|
||
// }
|
||
// }
|
||
|
||
// AssociatedObject.HistorySelectedItem = AssociatedObject.History.FirstOrDefault();
|
||
//}
|
||
|
||
|
||
private void UpdateHistory(string path)
|
||
{
|
||
if (AssociatedObject == null || _isNavigationCommand)
|
||
return;
|
||
|
||
try
|
||
{
|
||
// 检查是否已经是历史记录中的第一个项目
|
||
var firstHistoryItem = AssociatedObject.History.FirstOrDefault();
|
||
if (firstHistoryItem?.Model?.FullName == path)
|
||
return;
|
||
// 创建适当的目录模型
|
||
DirectoryModel historyItem;
|
||
if (string.IsNullOrEmpty(path) || path == RootDisplayName)
|
||
{
|
||
// 根路径时创建一个特殊的目录模型
|
||
historyItem = new DirectoryModel(new MinIODirectoryInfo("", "", true, DateTime.Now))
|
||
{
|
||
DisplayName = RootDisplayName
|
||
};
|
||
}
|
||
else
|
||
{
|
||
var parts = path.Split(new[] { '/' }, 2);
|
||
var bucketName = parts[0];
|
||
var prefix = parts.Length > 1 ? parts[1] : null;
|
||
|
||
var dirInfo = new MinIODirectoryInfo(bucketName, prefix, false, DateTime.Now);
|
||
historyItem = new DirectoryModel(dirInfo);
|
||
}
|
||
|
||
// 更新历史记录
|
||
_isLocalHistoryRefresh = true; // 防止递归触发
|
||
|
||
var newHistory = AssociatedObject.History.ToList();
|
||
newHistory.Insert(0, historyItem);
|
||
|
||
// 限制历史记录数量
|
||
AssociatedObject.History = newHistory
|
||
.Take(ExplorerSetting.Instance.HistCapacity)
|
||
.ToObservable();
|
||
|
||
AssociatedObject.HistorySelectedItem = AssociatedObject.History.FirstOrDefault();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.WriteLine($"UpdateHistory error: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
_isLocalHistoryRefresh = false;
|
||
}
|
||
}
|
||
|
||
|
||
//// 更新文件数量 $"已加载 {fileCount} 个文件"
|
||
//private void UpdateFileCount(string message)
|
||
//{
|
||
// _viewModel.UpdateStatus(message);
|
||
//}
|
||
#endregion
|
||
|
||
#region 图标处理
|
||
|
||
private static readonly Dictionary<string, System.Drawing.Icon> _iconCache = new Dictionary<string, System.Drawing.Icon>();
|
||
private const string DefaultFontFamily = "Segoe MDL2 Assets";
|
||
private const double DefaultIconSize = 16;
|
||
|
||
public static System.Drawing.Icon GetIconForMinIOItem(string name, bool isDirectory, bool isBucket = false)
|
||
{
|
||
var cacheKey = $"{(isBucket ? "bucket" : (isDirectory ? "folder" : Path.GetExtension(name).ToLower()))}";
|
||
|
||
if (!_iconCache.TryGetValue(cacheKey, out var icon))
|
||
{
|
||
icon = LoadMinIOIcon(name, isDirectory, isBucket);
|
||
_iconCache[cacheKey] = icon;
|
||
}
|
||
|
||
return icon;
|
||
}
|
||
|
||
private static System.Drawing.Icon LoadMinIOIcon(string name, bool isDirectory, bool isBucket)
|
||
{
|
||
// 获取字体图标字符
|
||
string iconChar = GetFontIconChar(name, isDirectory, isBucket);
|
||
|
||
// 创建字体图标ImageSource
|
||
var imageSource = CreateFontIconImageSource(iconChar);
|
||
|
||
// 转换为Icon
|
||
return ConvertRenderTargetBitmapToIcon(imageSource as RenderTargetBitmap);
|
||
}
|
||
|
||
private static System.Drawing.Icon ConvertRenderTargetBitmapToIcon(RenderTargetBitmap renderTargetBitmap)
|
||
{
|
||
// 将 RenderTargetBitmap 转为 Bitmap
|
||
var bitmap = new System.Drawing.Bitmap(
|
||
renderTargetBitmap.PixelWidth,
|
||
renderTargetBitmap.PixelHeight,
|
||
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
|
||
|
||
var data = bitmap.LockBits(
|
||
new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
|
||
System.Drawing.Imaging.ImageLockMode.WriteOnly,
|
||
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
|
||
|
||
renderTargetBitmap.CopyPixels(
|
||
System.Windows.Int32Rect.Empty,
|
||
data.Scan0,
|
||
data.Height * data.Stride,
|
||
data.Stride);
|
||
|
||
bitmap.UnlockBits(data);
|
||
|
||
// 从 Bitmap 创建 Icon
|
||
var iconHandle = bitmap.GetHicon();
|
||
return System.Drawing.Icon.FromHandle(iconHandle);
|
||
}
|
||
|
||
private static string GetFontIconChar(string name, bool isDirectory, bool isBucket)
|
||
{
|
||
// 使用Segoe MDL2 Assets字体中的字符代码
|
||
if (isBucket)
|
||
{
|
||
return "\ue7c3"; // Cloud icon
|
||
}
|
||
|
||
if (isDirectory)
|
||
{
|
||
return "\ue8b7"; // Folder icon
|
||
}
|
||
|
||
// 按文件类型返回不同图标
|
||
string extension = Path.GetExtension(name)?.ToLower();
|
||
|
||
switch (extension)
|
||
{
|
||
case ".pdf": return "\uea90";
|
||
case ".jpg":
|
||
case ".png":
|
||
case ".jpeg":
|
||
case ".gif":
|
||
case ".bmp":
|
||
case ".svg":
|
||
return "\ueb9f"; // Picture icon
|
||
case ".doc":
|
||
case ".docx":
|
||
return "\ue8a5"; // Word document icon
|
||
case ".xls":
|
||
case ".xlsx":
|
||
return "\ue8a6"; // Excel document icon
|
||
case ".ppt":
|
||
case ".pptx":
|
||
return "\ue8a7"; // PowerPoint icon
|
||
case ".zip":
|
||
case ".rar":
|
||
case ".7z":
|
||
return "\ue7b8"; // Zip folder icon
|
||
case ".txt":
|
||
case ".log":
|
||
return "\ue8ab"; // Text document icon
|
||
case ".mp3":
|
||
case ".wav":
|
||
case ".flac":
|
||
return "\ue8d6"; // Audio icon
|
||
case ".mp4":
|
||
case ".avi":
|
||
case ".mov":
|
||
case ".wmv":
|
||
return "\ue8b2"; // Video icon
|
||
case ".exe":
|
||
case ".msi":
|
||
return "\ue756"; // Application icon
|
||
case ".html":
|
||
case ".htm":
|
||
return "\ue842"; // HTML icon
|
||
case ".cs":
|
||
case ".cpp":
|
||
case ".js":
|
||
case ".java":
|
||
return "\ue9a2"; // Code icon
|
||
default:
|
||
return "\ue7c4"; // Default document icon
|
||
}
|
||
}
|
||
|
||
private static ImageSource CreateFontIconImageSource(
|
||
string iconChar,
|
||
string fontFamily = DefaultFontFamily,
|
||
double fontSize = DefaultIconSize,
|
||
System.Windows.Media.Brush foreground = null)
|
||
{
|
||
try
|
||
{
|
||
var textBlock = new System.Windows.Controls.TextBlock
|
||
{
|
||
Text = iconChar,
|
||
FontFamily = new System.Windows.Media.FontFamily(fontFamily),
|
||
FontSize = fontSize,
|
||
Foreground = foreground ?? System.Windows.Media.Brushes.Black,
|
||
HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
|
||
VerticalAlignment = System.Windows.VerticalAlignment.Center
|
||
};
|
||
|
||
// 计算实际需要的尺寸
|
||
textBlock.Measure(new System.Windows.Size(double.PositiveInfinity, double.PositiveInfinity));
|
||
textBlock.Arrange(new System.Windows.Rect(textBlock.DesiredSize));
|
||
|
||
var renderTargetBitmap = new RenderTargetBitmap(
|
||
(int)Math.Ceiling(textBlock.ActualWidth),
|
||
(int)Math.Ceiling(textBlock.ActualHeight),
|
||
96, 96, PixelFormats.Pbgra32);
|
||
|
||
renderTargetBitmap.Render(textBlock);
|
||
renderTargetBitmap.Freeze();
|
||
|
||
return renderTargetBitmap;
|
||
}
|
||
catch
|
||
{
|
||
// 回退到默认图标
|
||
return CreateFontIconImageSource("\ue7c4", fontFamily, fontSize, foreground);
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 新建文件夹
|
||
private SystemInfoModel _newFolderItem;
|
||
|
||
public void BeginEditNewFolder(SystemInfoModel newFolder)
|
||
{
|
||
if (AssociatedObject == null)
|
||
{
|
||
// 记录错误或尝试重新获取AssociatedObject
|
||
Debug.WriteLine("Error: AssociatedObject is null in BeginEditNewFolder");
|
||
return;
|
||
}
|
||
// 获取当前绑定的集合
|
||
var itemsSource = AssociatedObject.ItemsSource as ObservableCollection<SystemInfoModel>;
|
||
if (itemsSource == null) return;
|
||
// 添加到集合最前面
|
||
itemsSource.Insert(0, newFolder);
|
||
|
||
_newFolderItem = newFolder;
|
||
|
||
// 使用Dispatcher确保在UI线程执行
|
||
AssociatedObject.Dispatcher.InvokeAsync(() =>
|
||
{
|
||
// 查找DataGrid
|
||
var dataGrid = FindVisualChild<DataGrid>(AssociatedObject);
|
||
if (dataGrid == null)
|
||
{
|
||
Debug.WriteLine("Error: Could not find DataGrid in Explorer");
|
||
return;
|
||
}
|
||
|
||
// 选中新创建的文件夹
|
||
dataGrid.SelectedItem = newFolder;
|
||
|
||
// 开始编辑
|
||
dataGrid.Dispatcher.InvokeAsync(() =>
|
||
{
|
||
// 强制更新布局
|
||
dataGrid.UpdateLayout();
|
||
|
||
// 滚动到视图并获取行
|
||
dataGrid.ScrollIntoView(newFolder);
|
||
dataGrid.UpdateLayout(); // 再次更新布局确保行生成
|
||
|
||
var row = dataGrid.ItemContainerGenerator.ContainerFromItem(newFolder) as DataGridRow;
|
||
|
||
if (row == null)
|
||
{
|
||
// 如果仍然为空,尝试遍历所有行
|
||
foreach (var item in dataGrid.Items)
|
||
{
|
||
dataGrid.ScrollIntoView(item);
|
||
var tempRow = dataGrid.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow;
|
||
if (tempRow != null && tempRow.DataContext == newFolder)
|
||
{
|
||
row = tempRow;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (row != null)
|
||
{
|
||
var cell = GetCell(dataGrid, row, 1);
|
||
if (cell != null)
|
||
{
|
||
cell.Focus();
|
||
dataGrid.BeginEdit();
|
||
|
||
// 获取文本框并全选文本
|
||
var textBox = FindVisualChild<TextBox>(cell);
|
||
if (textBox != null)
|
||
{
|
||
textBox.SelectAll();
|
||
}
|
||
}
|
||
}
|
||
}, DispatcherPriority.Input);
|
||
}, DispatcherPriority.Background);
|
||
}
|
||
|
||
private void DataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
|
||
{
|
||
if (_newFolderItem == null || e.Row.DataContext != _newFolderItem)
|
||
return;
|
||
|
||
if (e.EditAction == DataGridEditAction.Commit)
|
||
{
|
||
//var textBox = e.EditingElement as TextBox;
|
||
// 使用递归查找TextBox
|
||
var textBox = FindVisualChild<TextBox>(e.EditingElement);
|
||
|
||
if (textBox != null)
|
||
{
|
||
var newName = textBox.Text.Trim();
|
||
|
||
if (!string.IsNullOrEmpty(newName))
|
||
{
|
||
// 更新模型名称
|
||
_newFolderItem.DisplayName = newName;
|
||
|
||
// 通知ViewModel在MinIO中创建文件夹
|
||
if (AssociatedObject.DataContext is LoyoutViewModel vm)
|
||
{
|
||
vm.CreateMinIOFolder(newName).ConfigureAwait(false);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 如果名称为空,移除该项
|
||
if (AssociatedObject.ItemsSource is ObservableCollection<SystemInfoModel> items)
|
||
{
|
||
items.Remove(_newFolderItem);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else if (e.EditAction == DataGridEditAction.Cancel)
|
||
{
|
||
// 如果取消编辑,移除该项
|
||
if (AssociatedObject.ItemsSource is ObservableCollection<SystemInfoModel> items)
|
||
{
|
||
items.Remove(_newFolderItem);
|
||
}
|
||
}
|
||
if (_newFolderItem is MinIODirectoryModel directoryModel)
|
||
{
|
||
directoryModel.IsRenaming = true; // 编辑完成后设为不可编辑
|
||
}
|
||
_newFolderItem = null;
|
||
}
|
||
|
||
|
||
private DataGridCell GetCell(DataGrid dataGrid, DataGridRow row, int column)
|
||
{
|
||
if (row == null) return null;
|
||
|
||
// 获取行的视觉子元素 - DataGridCellsPresenter
|
||
var presenter = FindVisualChild<DataGridCellsPresenter>(row);
|
||
if (presenter == null)
|
||
{
|
||
// 如果行是虚拟化的,可能需要先滚动到视图
|
||
dataGrid.ScrollIntoView(row, dataGrid.Columns[column]);
|
||
presenter = FindVisualChild<DataGridCellsPresenter>(row);
|
||
if (presenter == null) return null;
|
||
}
|
||
|
||
// 获取指定列的单元格
|
||
var cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
|
||
if (cell == null)
|
||
{
|
||
// 如果单元格尚未生成,再次尝试滚动到视图
|
||
dataGrid.ScrollIntoView(row, dataGrid.Columns[column]);
|
||
cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
|
||
}
|
||
|
||
return cell;
|
||
}
|
||
|
||
private static T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
|
||
{
|
||
if (parent == null) return null;
|
||
|
||
// 先检查parent本身是否是目标类型
|
||
if (parent is T result)
|
||
{
|
||
return result;
|
||
}
|
||
|
||
try
|
||
{
|
||
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
|
||
for (int i = 0; i < childrenCount; i++)
|
||
{
|
||
var child = VisualTreeHelper.GetChild(parent, i);
|
||
result = FindVisualChild<T>(child);
|
||
if (result != null)
|
||
{
|
||
return result;
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.WriteLine($"FindVisualChild error: {ex.Message}");
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private void DataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
|
||
{
|
||
if (e.Row.DataContext is MinIODirectoryModel directoryModel)
|
||
{
|
||
// 只有 IsRenaming 为 true 的文件夹可以编辑
|
||
e.Cancel = directoryModel.IsRenaming;
|
||
}
|
||
else
|
||
{
|
||
// 其他类型项目不可编辑
|
||
e.Cancel = true;
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region 导航栏属性
|
||
// 添加导航栏控制属性
|
||
public string RootDisplayName
|
||
{
|
||
get { return (string)GetValue(RootDisplayNameProperty); }
|
||
set { SetValue(RootDisplayNameProperty, value); }
|
||
}
|
||
|
||
public static readonly DependencyProperty RootDisplayNameProperty =
|
||
DependencyProperty.Register("RootDisplayName", typeof(string), typeof(ExplorerMinIOBehavior),
|
||
new PropertyMetadata("全部文件"));
|
||
|
||
|
||
private void OnExplorerLoaded(object sender, RoutedEventArgs e)
|
||
{
|
||
var navigationBar = FindVisualChild<NavigationBar>(AssociatedObject);
|
||
if (navigationBar != null)
|
||
{
|
||
// 替换导航栏的RefreshData方法
|
||
ReplaceNavigationBarRefreshMethod(navigationBar);
|
||
|
||
// 强制刷新导航栏
|
||
RefreshNavigationBar(navigationBar, AssociatedObject.CurrentPath);
|
||
}
|
||
}
|
||
|
||
private void ReplaceNavigationBarRefreshMethod(NavigationBar navigationBar)
|
||
{
|
||
// 获取原始RefreshData方法
|
||
var originalMethod = typeof(NavigationBar).GetMethod("RefreshData",
|
||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||
|
||
if (originalMethod == null) return;
|
||
|
||
// 动态创建委托替换原始方法
|
||
var methodDelegate = (Action<string>)delegate (string path)
|
||
{
|
||
RefreshNavigationBar(navigationBar, path);
|
||
};
|
||
|
||
// 使用反射替换方法(简化版,实际生产环境可能需要更安全的方式)
|
||
var field = typeof(NavigationBar).GetField("_refreshData",
|
||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||
field?.SetValue(navigationBar, methodDelegate);
|
||
}
|
||
|
||
private void RefreshNavigationBar(NavigationBar navigationBar, string path)
|
||
{
|
||
if (!UseMinIO)
|
||
{
|
||
// 本地文件系统模式,使用原始逻辑
|
||
var originalMethod = typeof(NavigationBar).GetMethod("OriginalRefreshData",
|
||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||
originalMethod?.Invoke(navigationBar, new object[] { path });
|
||
return;
|
||
}
|
||
|
||
// MinIO模式下的自定义路径处理
|
||
List<IDirectoryInfo> dirs = new List<IDirectoryInfo>();
|
||
|
||
if (string.IsNullOrEmpty(path) || path == "全部文件")
|
||
{
|
||
// 根节点显示自定义名称
|
||
var root = ExplorerProxy.Instance.CreateDirectoryInfo(RootDisplayName);
|
||
dirs.Add(root);
|
||
}
|
||
else
|
||
{
|
||
// 处理MinIO路径格式 bucketname/path/to/item
|
||
var parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||
string currentPath = "";
|
||
|
||
// 添加根节点
|
||
dirs.Add(ExplorerProxy.Instance.CreateDirectoryInfo(RootDisplayName));
|
||
|
||
// 添加各级路径
|
||
for (int i = 0; i < parts.Length; i++)
|
||
{
|
||
currentPath += (i > 0 ? "/" : "") + parts[i];
|
||
var dir = ExplorerProxy.Instance.CreateDirectoryInfo(currentPath);
|
||
dirs.Add(dir);
|
||
}
|
||
}
|
||
|
||
// 更新导航栏ItemsSource
|
||
navigationBar.ItemsSource = dirs.Select(l => new DirectoryModel(l));
|
||
}
|
||
#endregion
|
||
|
||
#region 搜索行为实现
|
||
|
||
#endregion
|
||
}
|
||
|
||
|
||
#region MinIO 模型类
|
||
|
||
public interface IMinIOFileInfo : ISystemFileInfo
|
||
{
|
||
string BucketName { get; }
|
||
bool IsDirectory { get; }
|
||
bool IsBucket { get; }
|
||
}
|
||
|
||
public class MinIOFileInfo : IMinIOFileInfo, IFileInfo
|
||
{
|
||
public MinIOFileInfo(string bucketName, string key, string size, DateTime? lastModified)
|
||
{
|
||
BucketName = bucketName;
|
||
FullName = $"{bucketName}/{key}";
|
||
Name = Path.GetFileName(key);
|
||
Size = size;
|
||
LastModified = lastModified;
|
||
}
|
||
|
||
public string BucketName { get; }
|
||
public bool IsDirectory => false;
|
||
public bool IsBucket => false;
|
||
public string Name { get; }
|
||
public string FullName { get; }
|
||
public string Size { get; }
|
||
public DateTime? LastModified { get; }
|
||
public DateTime? LastWriteTime => LastModified;
|
||
public FileAttributes Attributes => FileAttributes.Normal;
|
||
}
|
||
|
||
public class MinIODirectoryInfo : IMinIOFileInfo, IDirectoryInfo
|
||
{
|
||
public MinIODirectoryInfo(string bucketName, string key, bool isBucket, DateTime creationDate)
|
||
{
|
||
BucketName = bucketName;
|
||
FullName = string.IsNullOrEmpty(key) ? bucketName : $"{bucketName}/{key}";
|
||
Name = string.IsNullOrEmpty(key) ? bucketName : key.TrimEnd('/').Split('/').Last();
|
||
IsBucket = isBucket;
|
||
LastWriteTime = creationDate;
|
||
}
|
||
|
||
public string BucketName { get; }
|
||
public bool IsDirectory => true;
|
||
public bool IsBucket { get; }
|
||
public string Name { get; }
|
||
public string FullName { get; }
|
||
public DateTime LastWriteTime { get; }
|
||
public FileAttributes Attributes => FileAttributes.Directory;
|
||
}
|
||
|
||
public class MinIOFileModel : FileModel
|
||
{
|
||
public MinIOFileModel(IFileInfo model) : base(model)
|
||
{
|
||
this.Icon = "\ue7c4"; // 默认文件图标
|
||
}
|
||
|
||
public new Icon Logo => ExplorerMinIOBehavior.GetIconForMinIOItem(
|
||
this.Model.Name,
|
||
isDirectory: false);
|
||
private bool _isRenaming = true; // 默认不可编辑
|
||
|
||
public bool IsRenaming
|
||
{
|
||
get => _isRenaming;
|
||
set
|
||
{
|
||
if (_isRenaming != value)
|
||
{
|
||
_isRenaming = value;
|
||
RaisePropertyChanged(nameof(IsRenaming));
|
||
}
|
||
}
|
||
}
|
||
|
||
private bool _isChecked = false; // 默认不选中
|
||
|
||
public new bool IsChecked
|
||
{
|
||
get => _isChecked;
|
||
set
|
||
{
|
||
if (_isChecked != value)
|
||
{
|
||
_isChecked = value;
|
||
RaisePropertyChanged(nameof(IsChecked));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public class MinIODirectoryModel : DirectoryModel
|
||
{
|
||
public MinIODirectoryModel(IDirectoryInfo model) : base(model)
|
||
{
|
||
this.Icon = "\ue8b7"; // 默认文件夹图标
|
||
}
|
||
|
||
public new Icon Logo => ExplorerMinIOBehavior.GetIconForMinIOItem(
|
||
this.Model.Name,
|
||
isDirectory: true,
|
||
isBucket: (this.Model as IMinIOFileInfo)?.IsBucket ?? false);
|
||
|
||
private bool _isRenaming = true; // 默认不可编辑
|
||
|
||
public bool IsRenaming
|
||
{
|
||
get => _isRenaming;
|
||
set
|
||
{
|
||
if (_isRenaming != value)
|
||
{
|
||
_isRenaming = value;
|
||
RaisePropertyChanged(nameof(IsRenaming));
|
||
}
|
||
}
|
||
}
|
||
|
||
private bool _isChecked = false; // 默认不选中
|
||
|
||
public new bool IsChecked
|
||
{
|
||
get => _isChecked;
|
||
set
|
||
{
|
||
if (_isChecked != value)
|
||
{
|
||
_isChecked = value;
|
||
RaisePropertyChanged(nameof(IsChecked));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|