承接上篇核心遞迴掃描邏輯,本文完整呈現WinForms UI 層:資料夾選擇、非同步背景執行緒處理、事件驅動進度通知、執行緒安全 UI 更新、取消/完成狀態管理。可直接作為「批次檔案處理工具」的參考架構。
| 控制項 | Name | 關鍵屬性 |
|---|---|---|
| TextBox | txtFolder | ReadOnly=true, 寬度佔大部 |
| Button | btnBrowse | Text="瀏覽..." |
| TextBox | txtLog | Multiline=true, ReadOnly=true, ScrollBars=Vertical, Dock=Fill |
| Button | btnStart | Text="開始移除" |
| Button | btnCancel | Text="取消", Enabled=false |
// 事件參數:攜帶訊息字串
public class VssEventArgs : EventArgs
{
public string Message { get; }
public VssEventArgs(string msg) => Message = msg;
}
// 委派定義
public delegate void OperateNotifyHandler(object sender, VssEventArgs e);
// 背景工作類別
public class VssConverter
{
private readonly string _rootPath;
private int _processedCount;
public event OperateNotifyHandler OperateNotify;
public event EventHandler ThreadCompleted;
public VssConverter(string rootPath) => _rootPath = rootPath;
public void RemoveVss()
{
Task.Run(() =>
{
try
{
ProcessDirectory(_rootPath);
ThreadCompleted?.Invoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
OperateNotify?.Invoke(this, new VssEventArgs($"錯誤:{ex.Message}"));
ThreadCompleted?.Invoke(this, EventArgs.Empty);
}
});
}
private void ProcessDirectory(string dir)
{
// 遞迴掃描 .vssscc、mssccprj.scc 等 SourceSafe 檔案並刪除
// 每處理一個檔案/目錄觸發 OperateNotify
foreach (var file in Directory.GetFiles(dir, "*.vssscc"))
{
File.Delete(file);
OnNotify($"已刪除:{file}");
}
foreach (var subDir in Directory.GetDirectories(dir))
{
ProcessDirectory(subDir);
// 檢查空目錄刪除邏輯...
}
}
protected virtual void OnNotify(string msg)
{
OperateNotify?.Invoke(this, new VssEventArgs(msg));
}
}
public partial class MainForm : Form
{
private VssConverter _converter;
private int _convertState; // 0=閒置, 1=執行中, 2=完成
public MainForm() => InitializeComponent();
// 瀏覽資料夾
private void btnBrowse_Click(object sender, EventArgs e)
{
using (var dlg = new FolderBrowserDialog
{
Description = "選擇包含 SourceSafe 綁定的方案/專案根目錄",
ShowNewFolderButton = false
})
{
if (dlg.ShowDialog() == DialogResult.OK)
txtFolder.Text = dlg.SelectedPath;
}
}
// 開始處理
private void btnStart_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtFolder.Text) || !Directory.Exists(txtFolder.Text))
{
MessageBox.Show("請選擇有效的目錄路徑!");
return;
}
txtLog.Clear();
_convertState = 1;
SetUiBusy(true);
_converter = new VssConverter(txtFolder.Text);
_converter.OperateNotify += OnConverterNotify;
_converter.ThreadCompleted += OnConverterCompleted;
_converter.RemoveVss();
}
// 取消 (簡易版:標記狀態,背景執行緒需定期檢查 CancellationToken)
private void btnCancel_Click(object sender, EventArgs e)
{
_convertState = 0;
AppendLog("使用者取消操作...");
SetUiBusy(false);
}
// 背景執行緒通知 → UI 執行緒更新 (BeginInvoke)
private void OnConverterNotify(object sender, VssEventArgs e)
{
if (txtLog.IsDisposed) return;
txtLog.BeginInvoke(new Action(() => AppendLog(e.Message)));
}
private void AppendLog(string msg)
{
txtLog.AppendText($"[{DateTime.Now:HH:mm:ss}] {msg}{Environment.NewLine}");
txtLog.ScrollToCaret();
}
// 背景完成 → 恢復 UI
private void OnConverterCompleted(object sender, EventArgs e)
{
if (this.IsDisposed) return;
this.BeginInvoke(new Action(() =>
{
if (_convertState == 1) // 正常完成
{
AppendLog("#### 轉換完成 ####");
}
_convertState = 0;
SetUiBusy(false);
}));
}
private void SetUiBusy(bool busy)
{
btnStart.Enabled = !busy;
btnBrowse.Enabled = !busy;
btnCancel.Enabled = busy;
txtFolder.Enabled = !busy;
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
if (_convertState == 1)
{
if (MessageBox.Show("正在處理中,確定要關閉?", "確認", MessageBoxButtons.YesNo) == DialogResult.No)
e.Cancel = true;
}
base.OnFormClosing(e);
}
}
// 背景執行緒
txtLog.BeginInvoke(new Action(() => AppendLog(msg)));
// 等價於
txtLog.Invoke((MethodInvoker)delegate { AppendLog(msg); }); // 同步阻塞
BeginInvoke:非同步投遞,背景執行緒不等待,適合高頻日誌Invoke:同步阻塞,確保順序,適合關鍵狀態更新IsDisposed/IsHandleCreated 避免 ObjectDisposedExceptionVssConverter 僅定義 OperateNotify/ThreadCompleted 事件VssConverter 而無需 UI
private int _convertState; // 0=Idle, 1=Running, 2=Done
private void SetUiBusy(bool busy) { ... }
public class VssConverterModern
{
public async Task RemoveVssAsync(string rootPath, IProgress progress, CancellationToken ct)
{
await Task.Run(() =>
{
foreach (var file in Directory.EnumerateFiles(rootPath, "*.vssscc", SearchOption.AllDirectories))
{
ct.ThrowIfCancellationRequested();
File.Delete(file);
progress.Report($"已刪除:{file}");
}
}, ct);
}
}
// Form 中調用
private async void btnStart_Click(object sender, EventArgs e)
{
var progress = new Progress(msg => AppendLog(msg));
var cts = new CancellationTokenSource();
btnCancel.Click += (s, e) => cts.Cancel();
SetUiBusy(true);
try
{
await new VssConverterModern().RemoveVssAsync(txtFolder.Text, progress, cts.Token);
AppendLog("#### 完成 ####");
}
catch (OperationCanceledException)
{
AppendLog("已取消");
}
finally { SetUiBusy(false); }
}
| 檔案/模式 | 說明 | 處理動作 |
|---|---|---|
| *.vssscc | 專案/方案層級綁定資訊 | 刪除 |
| mssccprj.scc | 專案綁定資訊 (舊版) | 刪除 |
| *.vspscc | Web 專案綁定 | 刪除 |
| vssver.scc | 版本控制資訊 | 刪除 |
| .sln 中的 Scc 區塊 | 方案檔內嵌綁定資訊 | 移除 SccNumberOfProjects 等行 |
| .csproj/vbproj 中的 Scc 屬性 | 專案檔內嵌綁定資訊 | 移除 SccProjectName 等屬性 |
--dry-run 參數,僅列出將刪除檔案不實際刪除VssRemover_yyyyMMdd_HHmmss.log 供審計app.manifest 設 requireAdministrator 避免權限不足結語:這套「背景執行緒 + 事件通知 + BeginInvoke UI 更新」架構,是 WinForms 批次處理工具的黃金模板。核心優勢:邏輯與 UI 完全解耦、執行緒安全、狀態機防呆、易於遷移至 Task/async 現代模式。拿去套用到任何「掃描目錄、批次處理檔案、即時顯示進度」的工具開發中,即刻獲得專業級架構。