Compare commits
2 Commits
cfe2ccdf85
...
30567e48eb
| Author | SHA1 | Date |
|---|---|---|
|
|
30567e48eb | |
|
|
065f56cd2f |
|
|
@ -34,33 +34,33 @@ namespace HeBianGu.App.Disk
|
||||||
return await ViewAsync();
|
return await ViewAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> Image()
|
//public async Task<IActionResult> Image()
|
||||||
{
|
//{
|
||||||
this.ViewModel.Path = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
|
// this.ViewModel.Path = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
|
||||||
return await ViewAsync(nameof(Explorer));
|
// return await ViewAsync(nameof(Explorer));
|
||||||
}
|
//}
|
||||||
|
|
||||||
public async Task<IActionResult> Video()
|
//public async Task<IActionResult> Video()
|
||||||
{
|
//{
|
||||||
this.ViewModel.Path = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos);
|
// this.ViewModel.Path = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos);
|
||||||
return await ViewAsync(nameof(Explorer));
|
// return await ViewAsync(nameof(Explorer));
|
||||||
}
|
//}
|
||||||
public async Task<IActionResult> Music()
|
//public async Task<IActionResult> Music()
|
||||||
{
|
//{
|
||||||
this.ViewModel.Path = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic);
|
// this.ViewModel.Path = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic);
|
||||||
return await ViewAsync(nameof(Explorer));
|
// return await ViewAsync(nameof(Explorer));
|
||||||
}
|
//}
|
||||||
|
|
||||||
public async Task<IActionResult> Document()
|
//public async Task<IActionResult> Document()
|
||||||
{
|
//{
|
||||||
this.ViewModel.Path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
// this.ViewModel.Path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
||||||
return await ViewAsync(nameof(Explorer));
|
// return await ViewAsync(nameof(Explorer));
|
||||||
}
|
//}
|
||||||
|
|
||||||
public async Task<IActionResult> Recent()
|
//public async Task<IActionResult> Recent()
|
||||||
{
|
//{
|
||||||
this.ViewModel.Path = Environment.GetFolderPath(Environment.SpecialFolder.Recent);
|
// this.ViewModel.Path = Environment.GetFolderPath(Environment.SpecialFolder.Recent);
|
||||||
return await ViewAsync(nameof(Explorer));
|
// return await ViewAsync(nameof(Explorer));
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,14 @@ using System.Windows.Threading;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Media;
|
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;
|
||||||
|
|
||||||
namespace Hopetry.Provider.Behaviors
|
namespace Hopetry.Provider.Behaviors
|
||||||
{
|
{
|
||||||
|
|
@ -33,6 +41,17 @@ namespace Hopetry.Provider.Behaviors
|
||||||
public static readonly DependencyProperty UseMinIOProperty =
|
public static readonly DependencyProperty UseMinIOProperty =
|
||||||
DependencyProperty.Register("UseMinIO", typeof(bool), typeof(ExplorerMinIOBehavior),
|
DependencyProperty.Register("UseMinIO", typeof(bool), typeof(ExplorerMinIOBehavior),
|
||||||
new PropertyMetadata(false, OnUseMinIOChanged));
|
new PropertyMetadata(false, OnUseMinIOChanged));
|
||||||
|
|
||||||
|
//获取当前选中的文件
|
||||||
|
public IEnumerable<SystemInfoModel> GetSelectedItems()
|
||||||
|
{
|
||||||
|
var dataGrid = FindVisualChild<DataGrid>(AssociatedObject);
|
||||||
|
if (dataGrid != null)
|
||||||
|
{
|
||||||
|
return dataGrid.SelectedItems.Cast<SystemInfoModel>().ToList();
|
||||||
|
}
|
||||||
|
return Enumerable.Empty<SystemInfoModel>();
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 字段和初始化
|
#region 字段和初始化
|
||||||
|
|
@ -59,18 +78,67 @@ namespace Hopetry.Provider.Behaviors
|
||||||
{
|
{
|
||||||
Dispatcher.BeginInvoke((Action)(() =>
|
Dispatcher.BeginInvoke((Action)(() =>
|
||||||
{
|
{
|
||||||
var initialPath = string.IsNullOrEmpty(AssociatedObject.CurrentPath) ?
|
//var initialPath = string.IsNullOrEmpty(AssociatedObject.CurrentPath) ?
|
||||||
"" : AssociatedObject.CurrentPath;
|
// "" : AssociatedObject.CurrentPath;
|
||||||
|
|
||||||
RefreshMinIOPath(initialPath);
|
RefreshMinIOPath(AssociatedObject.CurrentPath);
|
||||||
|
//// 更新CurrentPath如果为空
|
||||||
// 更新CurrentPath如果为空
|
//if (string.IsNullOrEmpty(AssociatedObject.CurrentPath))
|
||||||
if (string.IsNullOrEmpty(AssociatedObject.CurrentPath))
|
//{
|
||||||
{
|
// AssociatedObject.CurrentPath = initialPath;
|
||||||
AssociatedObject.CurrentPath = initialPath;
|
//}
|
||||||
}
|
|
||||||
}), DispatcherPriority.Loaded);
|
}), 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保DataGrid正确初始化
|
||||||
|
//Dispatcher.BeginInvoke(new Action(() =>
|
||||||
|
//{
|
||||||
|
// var dataGrid = FindVisualChild<DataGrid>(AssociatedObject);
|
||||||
|
// if (dataGrid != null)
|
||||||
|
// {
|
||||||
|
// dataGrid.SelectionMode = DataGridSelectionMode.Extended;
|
||||||
|
// dataGrid.SelectionUnit = DataGridSelectionUnit.FullRow;
|
||||||
|
// }
|
||||||
|
//}), DispatcherPriority.Loaded);
|
||||||
|
AssociatedObject.Loaded += OnExplorerLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDetaching()
|
protected override void OnDetaching()
|
||||||
|
|
@ -84,6 +152,13 @@ namespace Hopetry.Provider.Behaviors
|
||||||
Explorer.HistoryProperty, typeof(Explorer));
|
Explorer.HistoryProperty, typeof(Explorer));
|
||||||
historyDescriptor.RemoveValueChanged(AssociatedObject, OnHistoryChanged);
|
historyDescriptor.RemoveValueChanged(AssociatedObject, OnHistoryChanged);
|
||||||
|
|
||||||
|
var dataGrid = FindVisualChild<DataGrid>(AssociatedObject);
|
||||||
|
if (dataGrid != null)
|
||||||
|
{
|
||||||
|
dataGrid.CellEditEnding -= DataGrid_CellEditEnding;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
base.OnDetaching();
|
base.OnDetaching();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,7 +210,7 @@ namespace Hopetry.Provider.Behaviors
|
||||||
.Build();
|
.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void RefreshMinIOPath(string path)
|
public async void RefreshMinIOPath(string path)
|
||||||
{
|
{
|
||||||
if (AssociatedObject == null || _minioClient == null) return;
|
if (AssociatedObject == null || _minioClient == null) return;
|
||||||
|
|
||||||
|
|
@ -147,6 +222,13 @@ namespace Hopetry.Provider.Behaviors
|
||||||
var items = await GetMinIOItemsAsync(path);
|
var items = await GetMinIOItemsAsync(path);
|
||||||
AssociatedObject.ItemsSource = items.ToObservable();
|
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
|
// Update history if this wasn't triggered by a history navigation
|
||||||
if (!_isLocalHistoryRefresh && !string.IsNullOrEmpty(path))
|
if (!_isLocalHistoryRefresh && !string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
|
|
@ -154,6 +236,7 @@ namespace Hopetry.Provider.Behaviors
|
||||||
}
|
}
|
||||||
|
|
||||||
_isLocalHistoryRefresh = false;
|
_isLocalHistoryRefresh = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -171,12 +254,12 @@ namespace Hopetry.Provider.Behaviors
|
||||||
{
|
{
|
||||||
var items = new List<SystemInfoModel>();
|
var items = new List<SystemInfoModel>();
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path)||path=="全部文件")
|
||||||
{
|
{
|
||||||
// List all buckets
|
// List all buckets
|
||||||
var buckets = await _minioClient.ListBucketsAsync();
|
var buckets = await _minioClient.ListBucketsAsync();
|
||||||
items.AddRange(buckets.Buckets.Select(b =>
|
items.AddRange(buckets.Buckets.Select(b =>
|
||||||
new MinIODirectoryModel(new MinIODirectoryInfo(b.Name,null,true, b.CreationDateDateTime))));
|
new MinIODirectoryModel(new MinIODirectoryInfo(b.Name, null, true, b.CreationDateDateTime))));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -189,51 +272,70 @@ namespace Hopetry.Provider.Behaviors
|
||||||
.WithBucket(bucketName)
|
.WithBucket(bucketName)
|
||||||
.WithPrefix(prefix ?? "")
|
.WithPrefix(prefix ?? "")
|
||||||
.WithRecursive(false);
|
.WithRecursive(false);
|
||||||
//var ss= new ListObjectsArgs()
|
|
||||||
// .WithBucket("demo")
|
|
||||||
// .WithPrefix("women")
|
|
||||||
// .WithRecursive(false);
|
|
||||||
|
|
||||||
var observable = _minioClient.ListObjectsEnumAsync(args);
|
var observable = _minioClient.ListObjectsEnumAsync(args);
|
||||||
//var observable1 = _minioClient.ListObjectsEnumAsync(ss);
|
|
||||||
|
|
||||||
await foreach (var item in observable)
|
await foreach (var item in observable)
|
||||||
{
|
{
|
||||||
|
// 解码为正确的字符串
|
||||||
|
var decodedKey = Uri.UnescapeDataString(item.Key);
|
||||||
if (item.IsDir)
|
if (item.IsDir)
|
||||||
{
|
{
|
||||||
items.Add(new MinIODirectoryModel(
|
items.Add(new MinIODirectoryModel(
|
||||||
new MinIODirectoryInfo(bucketName, item.Key, false, DateTime.Now)));
|
new MinIODirectoryInfo(bucketName, decodedKey, false, DateTime.Now)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string size = item.Size < 1024 * 1024 ?
|
if (!string.IsNullOrEmpty(Path.GetFileName(decodedKey)))
|
||||||
$"{Math.Ceiling((decimal)item.Size / 1024)}KB" :
|
{
|
||||||
$"{Math.Ceiling((decimal)item.Size / (1024 * 1024))}MB";
|
string size = item.Size < 1024 * 1024 ?
|
||||||
items.Add(new MinIOFileModel(
|
$"{Math.Ceiling((decimal)item.Size / 1024)}KB" :
|
||||||
new MinIOFileInfo(bucketName, item.Key, size, item.LastModifiedDateTime)));
|
$"{Math.Ceiling((decimal)item.Size / (1024 * 1024))}MB";
|
||||||
|
items.Add(new MinIOFileModel(new MinIOFileInfo(bucketName, decodedKey, size, item.LastModifiedDateTime)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
// 按修改时间降序排序(最新修改的排前面)
|
||||||
|
return items.OrderByDescending(x =>
|
||||||
|
{
|
||||||
|
if (x is MinIOFileModel fileModel)
|
||||||
|
{
|
||||||
|
return ((MinIOFileInfo)fileModel.Model).LastModified;
|
||||||
|
}
|
||||||
|
return DateTime.Now; // 文件夹使用当前时间(或根据需求调整)
|
||||||
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateHistory(string path)
|
private void UpdateHistory(string path)
|
||||||
{
|
{
|
||||||
var parts = path.Split(new[] { '/' }, 2);
|
if (string.IsNullOrEmpty(path))
|
||||||
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);
|
|
||||||
|
|
||||||
// Avoid duplicates
|
|
||||||
if (AssociatedObject.History.FirstOrDefault()?.Model?.FullName != path)
|
|
||||||
{
|
{
|
||||||
AssociatedObject.History.Insert(0, historyItem);
|
// 根路径时创建一个特殊的目录模型
|
||||||
AssociatedObject.History = AssociatedObject.History
|
var rootItem = new DirectoryModel(new MinIODirectoryInfo("", "", true, DateTime.Now))
|
||||||
.Take(ExplorerSetting.Instance.HistCapacity)
|
{
|
||||||
.ToObservable();
|
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();
|
AssociatedObject.HistorySelectedItem = AssociatedObject.History.FirstOrDefault();
|
||||||
|
|
@ -405,8 +507,302 @@ namespace Hopetry.Provider.Behaviors
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#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 = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#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))
|
||||||
|
{
|
||||||
|
// 根节点显示自定义名称
|
||||||
|
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 MinIO 模型类
|
#region MinIO 模型类
|
||||||
|
|
||||||
public interface IMinIOFileInfo : ISystemFileInfo
|
public interface IMinIOFileInfo : ISystemFileInfo
|
||||||
|
|
@ -468,6 +864,35 @@ namespace Hopetry.Provider.Behaviors
|
||||||
public new Icon Logo => ExplorerMinIOBehavior.GetIconForMinIOItem(
|
public new Icon Logo => ExplorerMinIOBehavior.GetIconForMinIOItem(
|
||||||
this.Model.Name,
|
this.Model.Name,
|
||||||
isDirectory: false);
|
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 class MinIODirectoryModel : DirectoryModel
|
||||||
|
|
@ -481,6 +906,36 @@ namespace Hopetry.Provider.Behaviors
|
||||||
this.Model.Name,
|
this.Model.Name,
|
||||||
isDirectory: true,
|
isDirectory: true,
|
||||||
isBucket: (this.Model as IMinIOFileInfo)?.IsBucket ?? false);
|
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
|
#endregion
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
using HeBianGu.Control.Explorer;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Hopetry.Provider
|
||||||
|
{
|
||||||
|
public static class NavigationBarExtensions
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty RootDisplayNameProperty =
|
||||||
|
DependencyProperty.RegisterAttached("RootDisplayName", typeof(string), typeof(NavigationBarExtensions),
|
||||||
|
new PropertyMetadata("全部文件", OnRootDisplayNameChanged));
|
||||||
|
|
||||||
|
public static string GetRootDisplayName(DependencyObject obj)
|
||||||
|
{
|
||||||
|
return (string)obj.GetValue(RootDisplayNameProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetRootDisplayName(DependencyObject obj, string value)
|
||||||
|
{
|
||||||
|
obj.SetValue(RootDisplayNameProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnRootDisplayNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is NavigationBar navigationBar)
|
||||||
|
{
|
||||||
|
// 这里可以通过反射或其他方式修改内部显示逻辑
|
||||||
|
// 或者依赖行为来处理实际修改
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,12 +29,19 @@
|
||||||
<Button h:Cattach.Icon="" Command="{Binding UploadCommand1}" Content="文件夹上传" Style="{DynamicResource {x:Static h:ButtonKeys.Dynamic}}" />
|
<Button h:Cattach.Icon="" Command="{Binding UploadCommand1}" Content="文件夹上传" Style="{DynamicResource {x:Static h:ButtonKeys.Dynamic}}" />
|
||||||
<!--<Button h:Cattach.Icon="" Content="下载" Style="{DynamicResource {x:Static h:ButtonKeys.Dynamic}}" />
|
<!--<Button h:Cattach.Icon="" Content="下载" Style="{DynamicResource {x:Static h:ButtonKeys.Dynamic}}" />
|
||||||
<Button h:Cattach.Icon="" Content="分享" Style="{DynamicResource {x:Static h:ButtonKeys.Dynamic}}" />-->
|
<Button h:Cattach.Icon="" Content="分享" Style="{DynamicResource {x:Static h:ButtonKeys.Dynamic}}" />-->
|
||||||
<!--<Button h:Cattach.Icon="" Content="删除" Style="{DynamicResource {x:Static h:ButtonKeys.Dynamic}}" />
|
<Button h:Cattach.Icon="" Command="{Binding DeleteFolderCommand}" Content="删除" Style="{DynamicResource {x:Static h:ButtonKeys.Dynamic}}" />
|
||||||
<Button h:Cattach.Icon="" Content="新建文件夹" Style="{DynamicResource {x:Static h:ButtonKeys.Dynamic}}" />
|
<Button h:Cattach.Icon="" Command="{Binding CreateFolderCommand}" Content="新建文件夹" Style="{DynamicResource {x:Static h:ButtonKeys.Dynamic}}" />
|
||||||
<Button h:Cattach.Icon="" Content="离线下载" Style="{DynamicResource {x:Static h:ButtonKeys.Dynamic}}" />-->
|
<!--<Button h:Cattach.Icon="" Content="离线下载" Style="{DynamicResource {x:Static h:ButtonKeys.Dynamic}}" />-->
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<h:Explorer Margin="0,0,10,0" CurrentPath="{Binding CurrentMinIOPath, Mode=TwoWay}" Style="{DynamicResource {x:Static h:Explorer.DefaultKey}}">
|
<h:Explorer Margin="0,0,10,0" CurrentPath="{Binding CurrentMinIOPath, Mode=TwoWay}"
|
||||||
|
Style="{DynamicResource {x:Static h:Explorer.DefaultKey}}"
|
||||||
|
IsReadOnly="False" SelectionMode="Extended"
|
||||||
|
EnableRowVirtualization="False"
|
||||||
|
EnableColumnVirtualization="False"
|
||||||
|
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
|
||||||
|
SelectionUnit="FullRow"
|
||||||
|
CanUserAddRows="False">
|
||||||
<h:Interaction.Behaviors>
|
<h:Interaction.Behaviors>
|
||||||
<!--<h:LoadAnimationBehavior End="0.05"
|
<!--<h:LoadAnimationBehavior End="0.05"
|
||||||
EndValue="1"
|
EndValue="1"
|
||||||
|
|
@ -42,40 +49,51 @@
|
||||||
LoadAnimationType="Opactiy"
|
LoadAnimationType="Opactiy"
|
||||||
Split="0.02"
|
Split="0.02"
|
||||||
StartValue="0" />-->
|
StartValue="0" />-->
|
||||||
<behaviors:ExplorerMinIOBehavior UseMinIO="True" />
|
<behaviors:ExplorerMinIOBehavior UseMinIO="True" RootDisplayName="全部文件" />
|
||||||
</h:Interaction.Behaviors>
|
</h:Interaction.Behaviors>
|
||||||
<!--<i:Interaction.Behaviors>
|
<!--<i:Interaction.Behaviors>
|
||||||
<behaviors:ExplorerMinIOBehavior UseMinIO="True" />
|
<behaviors:ExplorerMinIOBehavior UseMinIO="True" />
|
||||||
</i:Interaction.Behaviors>-->
|
</i:Interaction.Behaviors>-->
|
||||||
|
|
||||||
<h:Explorer.Columns>
|
<h:Explorer.Columns>
|
||||||
<DataGridTemplateColumn Width="50" Header="">
|
<DataGridTemplateColumn Width="50" Header="" >
|
||||||
<DataGridTemplateColumn.HeaderTemplate>
|
<DataGridTemplateColumn.HeaderTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<CheckBox Foreground="{Binding RelativeSource={RelativeSource AncestorType=DataGridColumnHeader}, Path=Foreground}" />
|
<CheckBox Foreground="{Binding RelativeSource={RelativeSource AncestorType=DataGridColumnHeader}, Path=Foreground}"/>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.HeaderTemplate>
|
</DataGridTemplateColumn.HeaderTemplate>
|
||||||
|
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<CheckBox Margin="0,0" HorizontalAlignment="Center" Foreground="{Binding RelativeSource={RelativeSource AncestorType=DataGridCell}, Path=Foreground}" />
|
<CheckBox Margin="0,0"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Foreground="{Binding RelativeSource={RelativeSource AncestorType=DataGridCell}, Path=Foreground}"
|
||||||
|
IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
Command="{Binding DataContext.SelectItemsCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}"
|
||||||
|
CommandParameter="{Binding}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn Width="3*" Header="文件名">
|
<DataGridTemplateColumn Width="3*" Header="文件名" IsReadOnly="False">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Image Margin="5" Source="{Binding Logo, Converter={x:Static h:XConverter.IconToImageSourceConverter}}" />
|
<Image Margin="5" Source="{Binding Logo, Converter={x:Static h:XConverter.IconToImageSourceConverter}}" />
|
||||||
<TextBlock Foreground="{Binding RelativeSource={RelativeSource AncestorType=DataGridCell}, Path=Foreground}" Style="{StaticResource {x:Static h:TextBlockKeys.Default}}" Text="{Binding Model.Name}" />
|
<TextBlock Foreground="{Binding RelativeSource={RelativeSource AncestorType=DataGridCell}, Path=Foreground}" Style="{StaticResource {x:Static h:TextBlockKeys.Default}}" Text="{Binding DisplayName}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataGridTemplateColumn.CellEditingTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBox Text="{Binding DisplayName, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
BorderThickness="0" Background="Transparent" IsReadOnly="{Binding IsRenaming}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellEditingTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTextColumn Width="*" Binding="{Binding Model.LastWriteTime, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}" Header="修改时间" />
|
<DataGridTextColumn Width="*" Binding="{Binding Model.LastWriteTime, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}" IsReadOnly="True" Header="修改时间" />
|
||||||
<DataGridTextColumn Width="*" Binding="{Binding Model.Size}" Header="大小" />
|
<DataGridTextColumn Width="*" Binding="{Binding Model.Size}" IsReadOnly="True" Header="大小" />
|
||||||
</h:Explorer.Columns>
|
</h:Explorer.Columns>
|
||||||
</h:Explorer>
|
</h:Explorer>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
using System.Windows.Controls;
|
using HeBianGu.Base.WpfBase;
|
||||||
|
using HeBianGu.Control.Explorer;
|
||||||
|
using Hopetry.Provider.Behaviors;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
namespace HeBianGu.App.Disk
|
namespace HeBianGu.App.Disk
|
||||||
{
|
{
|
||||||
|
|
@ -10,6 +15,45 @@ namespace HeBianGu.App.Disk
|
||||||
public ExplorerControl()
|
public ExplorerControl()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
this.Loaded += OnLoaded;
|
||||||
}
|
}
|
||||||
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// 查找h:Explorer控件
|
||||||
|
var explorer = FindVisualChild<Explorer>(this);
|
||||||
|
|
||||||
|
if (explorer != null && this.DataContext is LoyoutViewModel viewModel)
|
||||||
|
{
|
||||||
|
// 获取ExplorerBehavior并传递给ViewModel
|
||||||
|
var behavior = Interaction.GetBehaviors(explorer)
|
||||||
|
.OfType<ExplorerMinIOBehavior>()
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
viewModel.SetExplorerBehavior(behavior);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助方法:在可视化树中查找指定类型的子元素
|
||||||
|
private static T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
|
||||||
|
{
|
||||||
|
if (parent == null) return null;
|
||||||
|
|
||||||
|
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
||||||
|
{
|
||||||
|
var child = VisualTreeHelper.GetChild(parent, i);
|
||||||
|
if (child is T result)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var childResult = FindVisualChild<T>(child);
|
||||||
|
if (childResult != null)
|
||||||
|
{
|
||||||
|
return childResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,20 @@ using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using Microsoft.WindowsAPICodePack.Dialogs;
|
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||||
using Minio;
|
using Minio;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Hopetry.ViewModel.Send;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.AccessControl;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.WindowsAPICodePack.Dialogs;
|
||||||
|
using Hopetry.Models;
|
||||||
|
using Yitter.IdGenerator;
|
||||||
|
using Minio.Exceptions;
|
||||||
|
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
|
||||||
|
using HeBianGu.Control.Explorer;
|
||||||
|
using Hopetry.Provider.Behaviors;
|
||||||
|
using System.Text;
|
||||||
|
using Minio.ApiEndpoints;
|
||||||
using Minio.DataModel;
|
using Minio.DataModel;
|
||||||
using Minio.DataModel.Args;
|
using Minio.DataModel.Args;
|
||||||
using Timer = System.Timers.Timer;
|
using Timer = System.Timers.Timer;
|
||||||
|
|
@ -110,17 +124,22 @@ namespace HeBianGu.App.Disk
|
||||||
private CancellationTokenSource _uploadCancellation = new CancellationTokenSource();
|
private CancellationTokenSource _uploadCancellation = new CancellationTokenSource();
|
||||||
private int _uploadCount = 0;
|
private int _uploadCount = 0;
|
||||||
private int _completeCount = 0;
|
private int _completeCount = 0;
|
||||||
|
private ExplorerMinIOBehavior _explorerBehavior;
|
||||||
|
|
||||||
public ICommand UploadCommand { get; }
|
public ICommand UploadCommand { get; }
|
||||||
public ICommand UploadCommand1 { get; }
|
public ICommand UploadCommand1 { get; }
|
||||||
|
public ICommand CreateFolderCommand { get; }
|
||||||
|
public ICommand DeleteFolderCommand { get; }
|
||||||
|
//public ICommand SelectItemsCommand { get; }
|
||||||
public LoyoutViewModel(SendViewModel sendViewModel)
|
public LoyoutViewModel(SendViewModel sendViewModel)
|
||||||
{
|
{
|
||||||
_sendViewModel = sendViewModel;
|
_sendViewModel = sendViewModel;
|
||||||
_uploadService = new FileUploadService();
|
_uploadService = new FileUploadService();
|
||||||
UploadCommand = new AsyncRelayCommand(async () => await UploadFile());
|
UploadCommand = new AsyncRelayCommand(async () => await UploadFile());
|
||||||
UploadCommand1 = new AsyncRelayCommand(async () => await UploadFile1());
|
UploadCommand1 = new AsyncRelayCommand(async () => await UploadFile1());
|
||||||
|
CreateFolderCommand = new AsyncRelayCommand(async () => await CreateNewFolderAsync());
|
||||||
|
DeleteFolderCommand = new AsyncRelayCommand(async () => await DeleteSelectedItemsAsync());
|
||||||
|
//SelectItemsCommand = new AsyncRelayCommand(async () => await RelayCommand<SystemInfoModel>(SelectItem)SelectItem());
|
||||||
// 初始化Timer
|
// 初始化Timer
|
||||||
_progressTimer = new Timer(1000);
|
_progressTimer = new Timer(1000);
|
||||||
_progressTimer.Elapsed += UpdateProgress;
|
_progressTimer.Elapsed += UpdateProgress;
|
||||||
|
|
@ -129,8 +148,12 @@ namespace HeBianGu.App.Disk
|
||||||
_sendViewModel.UpdateUploadingItems();
|
_sendViewModel.UpdateUploadingItems();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// 添加设置Behavior的方法
|
||||||
private void UpdateProgress(object sender, ElapsedEventArgs e)
|
public void SetExplorerBehavior(ExplorerMinIOBehavior behavior)
|
||||||
|
{
|
||||||
|
_explorerBehavior = behavior;
|
||||||
|
}
|
||||||
|
private void UpdateProgress(object sender, System.Timers.ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
lock (_timerLock)
|
lock (_timerLock)
|
||||||
{
|
{
|
||||||
|
|
@ -435,16 +458,258 @@ namespace HeBianGu.App.Disk
|
||||||
|
|
||||||
#region 文件列表
|
#region 文件列表
|
||||||
private string _currentMinIOPath;
|
private string _currentMinIOPath;
|
||||||
/// <summary> 说明 </summary>
|
/// <summary> 说明CurrentMinIOPath </summary>
|
||||||
public string CurrentMinIOPath
|
public string CurrentMinIOPath
|
||||||
{
|
{
|
||||||
get { return _currentMinIOPath; }
|
get { return _currentMinIOPath; }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_currentMinIOPath = value;
|
// 确保路径格式正确
|
||||||
RaisePropertyChanged("CurrentMinIOPath");
|
var newPath = value;
|
||||||
|
if (!string.IsNullOrEmpty(newPath) && !newPath.EndsWith("/") &&
|
||||||
|
newPath.Contains("/") && !newPath.EndsWith("/"))
|
||||||
|
{
|
||||||
|
newPath += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentMinIOPath != newPath)
|
||||||
|
{
|
||||||
|
_currentMinIOPath = newPath;
|
||||||
|
RaisePropertyChanged("CurrentMinIOPath");
|
||||||
|
|
||||||
|
// 强制刷新导航栏
|
||||||
|
if (_explorerBehavior != null)
|
||||||
|
{
|
||||||
|
_explorerBehavior.RefreshMinIOPath(newPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public ObservableCollection<SystemInfoModel> Items { get; } = new ObservableCollection<SystemInfoModel>();
|
||||||
|
|
||||||
|
private async Task CreateNewFolderAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//// 获取当前绑定的集合
|
||||||
|
//var itemsSource = AssociatedObject.ItemsSource as ObservableCollection<SystemInfoModel>;
|
||||||
|
//if (itemsSource == null) return;
|
||||||
|
|
||||||
|
// 创建新的文件夹模型
|
||||||
|
var newFolder = new MinIODirectoryModel(
|
||||||
|
new MinIODirectoryInfo(GetCurrentBucket(), "新建文件夹/", false, DateTime.Now))
|
||||||
|
{
|
||||||
|
IsRenaming = false // 设置为可编辑状态
|
||||||
|
};
|
||||||
|
|
||||||
|
//// 添加到集合最前面
|
||||||
|
//itemsSource.Insert(0, newFolder);
|
||||||
|
|
||||||
|
// 通知行为类开始编辑
|
||||||
|
_explorerBehavior?.BeginEditNewFolder(newFolder);
|
||||||
|
|
||||||
|
await Task.Delay(100); // 等待UI更新
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"创建文件夹失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateMinIOFolder(string folderName)
|
||||||
|
{
|
||||||
|
var builder = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(Directory.GetCurrentDirectory())
|
||||||
|
.AddJsonFile("global.json", optional: false, reloadOnChange: true);
|
||||||
|
// 构建配置
|
||||||
|
var config = builder.Build();
|
||||||
|
// 从配置获取MinIO设置更安全
|
||||||
|
IMinioClient client = new Minio.MinioClient()
|
||||||
|
.WithEndpoint(config["Minio:Endpoint"])
|
||||||
|
.WithCredentials(config["Minio:AccessKey"], config["Minio:SecretKey"])
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(folderName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!folderName.EndsWith("/"))
|
||||||
|
folderName += "/";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bucketName = GetCurrentBucket();
|
||||||
|
var prefix = GetCurrentPrefix();
|
||||||
|
|
||||||
|
var beArgs = new BucketExistsArgs().WithBucket(bucketName);
|
||||||
|
bool found = await client.BucketExistsAsync(beArgs);
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
var mbArgs = new MakeBucketArgs().WithBucket(bucketName);
|
||||||
|
await client.MakeBucketAsync(mbArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var emptyStream = new MemoryStream(Encoding.UTF8.GetBytes(" ")))
|
||||||
|
{
|
||||||
|
await client.PutObjectAsync(
|
||||||
|
new PutObjectArgs()
|
||||||
|
.WithBucket(bucketName)
|
||||||
|
.WithObject(prefix + folderName)
|
||||||
|
.WithStreamData(emptyStream)
|
||||||
|
.WithObjectSize(1) // 必须设置为0
|
||||||
|
.WithContentType("application/x-directory"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新当前目录
|
||||||
|
_explorerBehavior?.RefreshMinIOPath(CurrentMinIOPath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"在MinIO中创建文件夹失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCurrentBucket()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(CurrentMinIOPath))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return CurrentMinIOPath.Split('/')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCurrentPrefix()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(CurrentMinIOPath) || CurrentMinIOPath.IndexOf('/') < 0)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return CurrentMinIOPath.Substring(CurrentMinIOPath.IndexOf('/') + 1);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 删除文件夹
|
||||||
|
private ObservableCollection<SystemInfoModel> _selectedItems = new ObservableCollection<SystemInfoModel>();
|
||||||
|
public ObservableCollection<SystemInfoModel> SelectedItems
|
||||||
|
{
|
||||||
|
get { return _selectedItems; }
|
||||||
|
set { _selectedItems = value; RaisePropertyChanged(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private ICommand _selectItemsCommand;
|
||||||
|
public ICommand SelectItemsCommand => _selectItemsCommand ?? (_selectItemsCommand = new RelayCommand<SystemInfoModel>(SelectItem));
|
||||||
|
|
||||||
|
private void SelectItem(SystemInfoModel item)
|
||||||
|
{
|
||||||
|
if (item == null) return;
|
||||||
|
|
||||||
|
if (SelectedItems.Contains(item))
|
||||||
|
{
|
||||||
|
SelectedItems.Remove(item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedItems.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private async Task DeleteSelectedItemsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var selectedItems = _selectedItems;
|
||||||
|
|
||||||
|
if (selectedItems == null || !selectedItems.Any())
|
||||||
|
{
|
||||||
|
MessageBox.Show("请先选择要删除的项目");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = $"确定要删除选中的 {selectedItems.Count} 个项目吗?";
|
||||||
|
var result = MessageBox.Show(message, "确认删除", MessageBoxButton.YesNo);
|
||||||
|
|
||||||
|
if (result == MessageBoxResult.Yes)
|
||||||
|
{
|
||||||
|
await DeleteMinIOItems(selectedItems);
|
||||||
|
|
||||||
|
_explorerBehavior.RefreshMinIOPath(CurrentMinIOPath);
|
||||||
|
MessageBox.Show($"已成功删除 {selectedItems.Count} 个项目");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"删除失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteMinIOItems(IEnumerable<SystemInfoModel> items)
|
||||||
|
{
|
||||||
|
var builder = new ConfigurationBuilder()
|
||||||
|
.SetBasePath(Directory.GetCurrentDirectory())
|
||||||
|
.AddJsonFile("global.json", optional: false, reloadOnChange: true);
|
||||||
|
var config = builder.Build();
|
||||||
|
|
||||||
|
using var client = new MinioClient()
|
||||||
|
.WithEndpoint(config["Minio:Endpoint"])
|
||||||
|
.WithCredentials(config["Minio:AccessKey"], config["Minio:SecretKey"])
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// 按桶分组
|
||||||
|
var itemsByBucket = items.GroupBy(item =>
|
||||||
|
{
|
||||||
|
if (item is MinIOFileModel file)
|
||||||
|
return ((MinIOFileInfo)file.Model).BucketName;
|
||||||
|
if (item is MinIODirectoryModel dir)
|
||||||
|
return ((MinIODirectoryInfo)dir.Model).BucketName;
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var bucketGroup in itemsByBucket.Where(g => g.Key != null))
|
||||||
|
{
|
||||||
|
var bucketName = bucketGroup.Key;
|
||||||
|
var objectsToDelete = new List<string>();
|
||||||
|
|
||||||
|
foreach (var item in bucketGroup)
|
||||||
|
{
|
||||||
|
if (item is MinIOFileModel fileModel)
|
||||||
|
{
|
||||||
|
var fileInfo = (MinIOFileInfo)fileModel.Model;
|
||||||
|
objectsToDelete.Add(fileInfo.FullName.Substring(fileInfo.BucketName.Length + 1));
|
||||||
|
}
|
||||||
|
else if (item is MinIODirectoryModel dirModel)
|
||||||
|
{
|
||||||
|
var dirInfo = (MinIODirectoryInfo)dirModel.Model;
|
||||||
|
var folderPath = dirInfo.FullName.Substring(dirInfo.BucketName.Length + 1);
|
||||||
|
|
||||||
|
// 确保文件夹路径以斜杠结尾
|
||||||
|
if (!folderPath.EndsWith("/")) folderPath += "/";
|
||||||
|
|
||||||
|
// 获取文件夹下所有对象
|
||||||
|
var listArgs = new ListObjectsArgs()
|
||||||
|
.WithBucket(bucketName)
|
||||||
|
.WithPrefix(folderPath)
|
||||||
|
.WithRecursive(true);
|
||||||
|
|
||||||
|
await foreach (var obj in client.ListObjectsEnumAsync(listArgs))
|
||||||
|
{
|
||||||
|
objectsToDelete.Add(obj.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加文件夹标记本身
|
||||||
|
objectsToDelete.Add(folderPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
if (objectsToDelete.Any())
|
||||||
|
{
|
||||||
|
await client.RemoveObjectsAsync(
|
||||||
|
new RemoveObjectsArgs()
|
||||||
|
.WithBucket(bucketName)
|
||||||
|
.WithObjects(objectsToDelete));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -455,8 +720,6 @@ namespace HeBianGu.App.Disk
|
||||||
{
|
{
|
||||||
base.Init();
|
base.Init();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue