SlideCombine/Form1.cs
yuuko 3b8326f213 实现类似Windows文件管理器的文件夹选择界面
创建了一个完整仿Windows资源管理器的界面:

1. 界面布局:
   - 顶部:路径工具栏(路径输入框 + 浏览按钮)
   - 左侧:文件夹树形视图(显示所有驱动器和文件夹结构)
   - 右侧:文件列表视图(详细列表,包含名称、日期、类型、大小)
   - 底部:状态栏(显示文件夹和文件统计信息)

2. 功能特性:
   - 树形文件夹结构导航
   - 实时文件列表显示
   - 支持键盘操作(回车确认路径)
   - 双击文件夹快速导航
   - 动态加载子文件夹(按需加载提高性能)
   - 文件类型识别和大小格式化
   - 详细的状态信息显示

3. 用户体验:
   - 更大的界面尺寸(800x600)
   - 类似Windows资源管理器的操作习惯
   - 左右分栏设计,信息展示更清晰
   - 支持多种导航方式(树形、列表、路径输入)

4. 性能优化:
   - 懒加载子文件夹,避免初始加载过慢
   - 异步处理UI更新
   - 错误处理和权限检查

现在的文件夹选择界面更加专业,完全模拟了Windows文件管理器的体验!

🤖 Generated with [Claude Code](https://claude.com/claude.code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-24 16:25:31 +08:00

668 lines
26 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
namespace SlideCombine
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// 设置窗体图标 - 使用嵌入资源
try
{
var stream = GetType().Assembly.GetManifestResourceStream("SlideCombine.app.ico");
if (stream != null)
{
this.Icon = new Icon(stream);
}
}
catch
{
// 如果图标加载失败,使用默认图标
// this.Icon = SystemIcons.Application;
}
}
private void btnBrowseSource_Click(object sender, EventArgs e)
{
string selectedPath = ShowModernFolderBrowser("请选择包含PDF文件夹的路径含FreePic2Pdf_bkmk.txt文件", txtSourcePath.Text);
if (!string.IsNullOrEmpty(selectedPath))
{
txtSourcePath.Text = selectedPath;
LogInfo($"已选择PDF路径: {selectedPath}");
}
}
private void btnBrowseText_Click(object sender, EventArgs e)
{
string selectedPath = ShowModernFolderBrowser("请选择包含元数据TXT文件的路径", txtTextPath.Text);
if (!string.IsNullOrEmpty(selectedPath))
{
txtTextPath.Text = selectedPath;
LogInfo($"已选择TXT源路径: {selectedPath}");
}
}
private void btnBrowseOutput_Click(object sender, EventArgs e)
{
string selectedPath = ShowModernFolderBrowser("请选择合并后TXT文件的输出路径", txtOutputPath.Text);
if (!string.IsNullOrEmpty(selectedPath))
{
txtOutputPath.Text = selectedPath;
LogInfo($"已选择最终输出路径: {selectedPath}");
}
}
private void btnPasteSource_Click(object sender, EventArgs e)
{
if (Clipboard.ContainsText())
{
var pastedPath = Clipboard.GetText().Trim();
if (!string.IsNullOrEmpty(pastedPath))
{
txtSourcePath.Text = pastedPath;
LogInfo($"已粘贴PDF路径: {pastedPath}");
}
}
else
{
MessageBox.Show("剪贴板中没有文本内容", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
private void btnPasteText_Click(object sender, EventArgs e)
{
if (Clipboard.ContainsText())
{
var pastedPath = Clipboard.GetText().Trim();
if (!string.IsNullOrEmpty(pastedPath))
{
txtTextPath.Text = pastedPath;
LogInfo($"已粘贴TXT源路径: {pastedPath}");
}
}
else
{
MessageBox.Show("剪贴板中没有文本内容", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
private void btnPasteOutput_Click(object sender, EventArgs e)
{
if (Clipboard.ContainsText())
{
var pastedPath = Clipboard.GetText().Trim();
if (!string.IsNullOrEmpty(pastedPath))
{
txtOutputPath.Text = pastedPath;
LogInfo($"已粘贴输出路径: {pastedPath}");
}
}
else
{
MessageBox.Show("剪贴板中没有文本内容", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
private void btnClear_Click(object sender, EventArgs e)
{
txtSourcePath.Clear();
txtTextPath.Clear();
txtOutputPath.Clear();
txtLog.Clear();
progressBar.Value = 0;
LogInfo("界面已清空");
}
private void btnExit_Click(object sender, EventArgs e)
{
LogInfo("程序即将退出");
Application.Exit();
}
private void btnMerge_Click(object sender, EventArgs e)
{
try
{
// 验证输入
if (string.IsNullOrWhiteSpace(txtSourcePath.Text) ||
string.IsNullOrWhiteSpace(txtTextPath.Text) ||
string.IsNullOrWhiteSpace(txtOutputPath.Text))
{
MessageBox.Show("请选择所有三个路径PDF路径、TXT源路径和输出路径", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (!Directory.Exists(txtSourcePath.Text))
{
MessageBox.Show("指定的PDF文件夹路径不存在", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (!Directory.Exists(txtTextPath.Text))
{
MessageBox.Show("指定的TXT源文件路径不存在", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// 禁用按钮,防止重复点击
btnMerge.Enabled = false;
btnClear.Enabled = false;
// 重置进度条和日志
progressBar.Value = 0;
txtLog.Clear();
Log("开始处理PDF书签文件...");
// 处理文件
var results = FileMerger.ProcessAllFolders(txtSourcePath.Text, txtTextPath.Text, txtOutputPath.Text);
// 显示进度
progressBar.Value = 50;
Log($"找到 {results.Count} 个文件组需要处理");
// 保存结果
FileMerger.SaveResults(results, txtOutputPath.Text);
progressBar.Value = 100;
// 统计成功和失败的数量
int successCount = 0;
int failCount = 0;
var sb = new StringBuilder();
foreach (var result in results)
{
if (result.Success)
{
successCount++;
Log($"✓ 成功处理: {result.BaseFileName} (合并了 {result.SourceFiles.Count} 个文件)");
}
else
{
failCount++;
Log($"✗ 处理失败: {result.ErrorMessage}");
}
}
Log($"处理完成! 成功: {successCount}, 失败: {failCount}");
if (successCount > 0)
{
MessageBox.Show($"书签合并完成!\n成功处理 {successCount} 个文件\n输出路径: {txtOutputPath.Text}",
"处理完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
MessageBox.Show("没有成功处理任何文件,请检查输入路径和文件格式。",
"处理失败", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
catch (Exception ex)
{
Log($"错误: {ex.Message}");
MessageBox.Show($"处理过程中发生错误:\n{ex.Message}",
"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
// 重新启用按钮
btnMerge.Enabled = true;
btnClear.Enabled = true;
}
}
private void Log(string msg)
{
string timestamp = DateTime.Now.ToString("HH:mm:ss");
txtLog.AppendText($"[{timestamp}] {msg}\r\n");
txtLog.ScrollToCaret();
Application.DoEvents();
}
private void LogSuccess(string msg)
{
string timestamp = DateTime.Now.ToString("HH:mm:ss");
txtLog.SelectionStart = txtLog.TextLength;
txtLog.SelectionColor = Color.Green;
txtLog.AppendText($"[{timestamp}] ✅ {msg}\r\n");
txtLog.SelectionColor = txtLog.ForeColor;
txtLog.ScrollToCaret();
Application.DoEvents();
}
private void LogError(string msg)
{
string timestamp = DateTime.Now.ToString("HH:mm:ss");
txtLog.SelectionStart = txtLog.TextLength;
txtLog.SelectionColor = Color.Red;
txtLog.AppendText($"[{timestamp}] ❌ {msg}\r\n");
txtLog.SelectionColor = txtLog.ForeColor;
txtLog.ScrollToCaret();
Application.DoEvents();
}
private void LogInfo(string msg)
{
string timestamp = DateTime.Now.ToString("HH:mm:ss");
txtLog.SelectionStart = txtLog.TextLength;
txtLog.SelectionColor = Color.Blue;
txtLog.AppendText($"[{timestamp}] {msg}\r\n");
txtLog.SelectionColor = txtLog.ForeColor;
txtLog.ScrollToCaret();
Application.DoEvents();
}
private string ShowModernFolderBrowser(string title, string initialPath)
{
using (var form = new Form())
{
form.Text = title;
form.Size = new Size(800, 600);
form.StartPosition = FormStartPosition.CenterParent;
form.FormBorderStyle = FormBorderStyle.FixedDialog;
form.MaximizeBox = false;
form.MinimizeBox = false;
form.BackColor = Color.White;
// 顶部工具栏
var toolbarPanel = new Panel();
toolbarPanel.Location = new Point(0, 0);
toolbarPanel.Size = new Size(800, 60);
toolbarPanel.BackColor = Color.FromArgb(248, 248, 248);
toolbarPanel.BorderStyle = BorderStyle.FixedSingle;
form.Controls.Add(toolbarPanel);
// 路径标签
var pathLabel = new Label();
pathLabel.Text = "路径:";
pathLabel.Location = new Point(15, 20);
pathLabel.Size = new Size(40, 20);
pathLabel.Font = new Font("Microsoft YaHei", 9F);
pathLabel.ForeColor = Color.FromArgb(51, 51, 51);
toolbarPanel.Controls.Add(pathLabel);
// 路径文本框
var pathTextBox = new TextBox();
pathTextBox.Location = new Point(60, 18);
pathTextBox.Size = new Size(650, 24);
pathTextBox.Font = new Font("Microsoft YaHei", 10F);
pathTextBox.Text = initialPath ?? string.Empty;
pathTextBox.BorderStyle = BorderStyle.FixedSingle;
toolbarPanel.Controls.Add(pathTextBox);
// 路径浏览按钮
var browseButton = new Button();
browseButton.Text = "...";
browseButton.Location = new Point(720, 17);
browseButton.Size = new Size(30, 24);
browseButton.Font = new Font("Microsoft YaHei", 9F);
browseButton.BackColor = Color.FromArgb(66, 139, 202);
browseButton.ForeColor = Color.White;
browseButton.FlatStyle = FlatStyle.Flat;
browseButton.FlatAppearance.BorderSize = 0;
toolbarPanel.Controls.Add(browseButton);
// 左侧文件夹树
var folderTree = new TreeView();
folderTree.Location = new Point(15, 70);
folderTree.Size = new Size(250, 450);
folderTree.Font = new Font("Microsoft YaHei", 9F);
folderTree.BorderStyle = BorderStyle.FixedSingle;
folderTree.ShowLines = true;
folderTree.ShowPlusMinus = true;
folderTree.ShowRootLines = true;
folderTree.FullRowSelect = true;
folderTree.Scrollable = true;
form.Controls.Add(folderTree);
// 右侧文件列表
var fileList = new ListView();
fileList.Location = new Point(275, 70);
fileList.Size = new Size(510, 450);
fileList.View = View.Details;
fileList.FullRowSelect = true;
fileList.GridLines = true;
fileList.Font = new Font("Microsoft YaHei", 9F);
fileList.BorderStyle = BorderStyle.FixedSingle;
fileList.Columns.Add("名称", 200);
fileList.Columns.Add("修改日期", 150);
fileList.Columns.Add("类型", 80);
fileList.Columns.Add("大小", 80);
form.Controls.Add(fileList);
// 底部状态栏
var statusPanel = new Panel();
statusPanel.Location = new Point(0, 530);
statusPanel.Size = new Size(800, 30);
statusPanel.BackColor = Color.FromArgb(240, 240, 240);
statusPanel.BorderStyle = BorderStyle.FixedSingle;
form.Controls.Add(statusPanel);
var statusLabel = new Label();
statusLabel.Text = "就绪";
statusLabel.Location = new Point(10, 5);
statusLabel.Size = new Size(780, 20);
statusLabel.Font = new Font("Microsoft YaHei", 9F);
statusLabel.ForeColor = Color.FromArgb(102, 102, 102);
statusPanel.Controls.Add(statusLabel);
// 底部按钮
var okButton = new Button();
okButton.Text = "确定";
okButton.Location = new Point(620, 20);
okButton.Size = new Size(80, 35);
okButton.Font = new Font("Microsoft YaHei", 10F, FontStyle.Bold);
okButton.BackColor = Color.FromArgb(92, 184, 92);
okButton.ForeColor = Color.White;
okButton.FlatStyle = FlatStyle.Flat;
okButton.FlatAppearance.BorderSize = 0;
form.AcceptButton = okButton;
form.Controls.Add(okButton);
var cancelButton = new Button();
cancelButton.Text = "取消";
cancelButton.Location = new Point(710, 20);
cancelButton.Size = new Size(80, 35);
cancelButton.Font = new Font("Microsoft YaHei", 10F);
cancelButton.BackColor = Color.FromArgb(217, 83, 79);
cancelButton.ForeColor = Color.White;
cancelButton.FlatStyle = FlatStyle.Flat;
cancelButton.FlatAppearance.BorderSize = 0;
form.CancelButton = cancelButton;
form.Controls.Add(cancelButton);
// 当前路径
string currentPath = string.IsNullOrEmpty(initialPath) ?
Environment.GetFolderPath(Environment.SpecialFolder.Desktop) : initialPath;
// 加载文件夹树
void LoadFolderTree()
{
folderTree.Nodes.Clear();
folderTree.BeginUpdate();
try
{
// 获取所有逻辑驱动器
foreach (var drive in DriveInfo.GetDrives())
{
var driveNode = new TreeNode();
driveNode.Text = $"{drive.Name} ({drive.VolumeTitle})";
driveNode.Tag = drive.Name;
if (drive.IsReady)
{
driveNode.Nodes.Add(new TreeNode()); // 占位节点
}
driveNode.ImageIndex = 0;
driveNode.SelectedImageIndex = 0;
folderTree.Nodes.Add(driveNode);
}
}
catch (Exception ex)
{
statusLabel.Text = $"加载驱动器时出错: {ex.Message}";
}
folderTree.EndUpdate();
}
// 展开节点时加载子文件夹
void LoadSubFolders(TreeNode parentNode)
{
if (parentNode.Nodes.Count == 1 && parentNode.Nodes[0].Text == "")
{
parentNode.Nodes.Clear();
try
{
var path = parentNode.Tag as string;
if (Directory.Exists(path))
{
foreach (var dir in Directory.GetDirectories(path))
{
var dirInfo = new DirectoryInfo(dir);
var node = new TreeNode(dirInfo.Name);
node.Tag = dirInfo.FullName;
try
{
// 检查是否有子文件夹
if (Directory.GetDirectories(dir).Length > 0)
{
node.Nodes.Add(new TreeNode()); // 占位节点
}
}
catch
{
// 无权限访问子文件夹
}
parentNode.Nodes.Add(node);
}
}
}
catch (Exception ex)
{
statusLabel.Text = $"加载子文件夹时出错: {ex.Message}";
}
}
}
// 加载文件列表
void LoadFileList(string path)
{
fileList.Items.Clear();
fileList.BeginUpdate();
try
{
if (Directory.Exists(path))
{
// 添加返回上级目录
var parentPath = Path.GetDirectoryName(path);
if (!string.IsNullOrEmpty(parentPath))
{
var parentItem = new ListViewItem(new[] { "..", "", "文件夹", "" });
parentItem.Tag = parentPath;
parentItem.ForeColor = Color.FromArgb(66, 139, 202);
fileList.Items.Add(parentItem);
}
// 添加文件夹
foreach (var dir in Directory.GetDirectories(path))
{
var dirInfo = new DirectoryInfo(dir);
var item = new ListViewItem(new[]
{
dirInfo.Name,
dirInfo.LastWriteTime.ToString("yyyy-MM-dd HH:mm"),
"文件夹",
""
});
item.Tag = dirInfo.FullName;
item.ForeColor = Color.FromArgb(51, 51, 51);
fileList.Items.Add(item);
}
// 添加文件
foreach (var file in Directory.GetFiles(path))
{
var fileInfo = new FileInfo(file);
var item = new ListViewItem(new[]
{
fileInfo.Name,
fileInfo.LastWriteTime.ToString("yyyy-MM-dd HH:mm"),
GetFileType(fileInfo.Extension),
FormatFileSize(fileInfo.Length)
});
item.Tag = fileInfo.FullName;
item.ForeColor = Color.FromArgb(102, 102, 102);
fileList.Items.Add(item);
}
statusLabel.Text = $"{Directory.GetDirectories(path).Length} 个文件夹, {Directory.GetFiles(path).Length} 个文件";
}
}
catch (Exception ex)
{
statusLabel.Text = $"加载文件列表时出错: {ex.Message}";
}
fileList.EndUpdate();
}
string GetFileType(string extension)
{
extension = extension.ToLower();
switch (extension)
{
case ".txt": return "文本文件";
case ".pdf": return "PDF文档";
case ".doc": case ".docx": return "Word文档";
case ".xls": case ".xlsx": return "Excel表格";
default: return $"{extension}文件";
}
}
string FormatFileSize(long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB" };
double len = bytes;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1)
{
order++;
len = len / 1024;
}
return $"{len:0.##} {sizes[order]}";
}
// 事件处理
folderTree.BeforeExpand += (s, e) =>
{
LoadSubFolders(e.Node);
};
folderTree.AfterSelect += (s, e) =>
{
if (e.Node.Tag is string path && Directory.Exists(path))
{
currentPath = path;
pathTextBox.Text = currentPath;
LoadFileList(currentPath);
}
};
fileList.DoubleClick += (s, e) =>
{
if (fileList.SelectedItems.Count > 0)
{
var selectedItem = fileList.SelectedItems[0];
if (selectedItem.Tag is string selectedPath && Directory.Exists(selectedPath))
{
currentPath = selectedPath;
pathTextBox.Text = currentPath;
LoadFileList(currentPath);
// 更新树选择
var nodes = folderTree.Nodes.Find(GetNodeText(selectedPath), true);
if (nodes.Length > 0)
{
folderTree.SelectedNode = nodes[0];
}
}
}
};
browseButton.Click += (s, e) =>
{
using (var dialog = new FolderBrowserDialog())
{
dialog.Description = title;
dialog.SelectedPath = currentPath;
if (dialog.ShowDialog() == DialogResult.OK)
{
currentPath = dialog.SelectedPath;
pathTextBox.Text = currentPath;
LoadFileList(currentPath);
// 更新树选择
var nodes = folderTree.Nodes.Find(GetNodeText(currentPath), true);
if (nodes.Length > 0)
{
folderTree.SelectedNode = nodes[0];
}
}
}
};
pathTextBox.KeyDown += (s, e) =>
{
if (e.KeyCode == Keys.Enter)
{
if (Directory.Exists(pathTextBox.Text))
{
currentPath = pathTextBox.Text;
LoadFileList(currentPath);
// 更新树选择
var nodes = folderTree.Nodes.Find(GetNodeText(currentPath), true);
if (nodes.Length > 0)
{
folderTree.SelectedNode = nodes[0];
}
}
}
};
string GetNodeText(string fullPath)
{
return new DirectoryInfo(fullPath).Name;
}
okButton.Click += (s, e) =>
{
if (Directory.Exists(pathTextBox.Text))
{
form.DialogResult = DialogResult.OK;
form.Close();
}
else
{
MessageBox.Show("请输入有效的文件夹路径", "提示",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
};
cancelButton.Click += (s, e) =>
{
form.DialogResult = DialogResult.Cancel;
form.Close();
};
// 初始化
LoadFolderTree();
LoadFileList(currentPath);
// 显示对话框
if (form.ShowDialog() == DialogResult.OK)
{
return pathTextBox.Text;
}
}
return null;
}
}
}