承接上集服務核心實現,本文完善 **WinForm 客戶端控制介面**、**系統配置物件 (MyConfig) 與 Access 資料庫持久化**、**服務安裝/解除安裝/啟動/停止/暫停/繼續** 完整生命週期管理,並詳細說明 **命令列參數路由**、**InstallUtil 部署**、**除錯模式** 等生產級部署細節。
ServiceController.Status 更新按鈕啟用狀態
// 啟動服務
private void btnStartService_Click(object sender, EventArgs e)
{
if (!_serviceInstalled) return;
using (var sc = new ServiceController("MyFileSystemWatcher"))
{
if (sc.Status == ServiceControllerStatus.Stopped)
sc.Start(); // 非阻塞,發送啟動命令即返回
}
}
// 停止服務
private void btnStopService_Click(object sender, EventArgs e)
{
if (!_serviceInstalled) return;
using (var sc = new ServiceController("MyFileSystemWatcher"))
{
if (sc.Status == ServiceControllerStatus.Running)
sc.Stop(); // 非阻塞
}
}
// Timer 輪詢更新 UI
private void timer_Tick(object sender, EventArgs e)
{
if (!_serviceInstalled) return;
using (var sc = new ServiceController("MyFileSystemWatcher"))
{
btnStartService.Enabled = (sc.Status == ServiceControllerStatus.Stopped);
btnStopService.Enabled = (sc.Status == ServiceControllerStatus.Running);
}
}
對應 MyConfig 物件屬性:
CheckBox)ListBox + 新增/移除按鈕,支援通配符 (如 C:\Data\*.txt、D:\)MyConfig.Instance.Save() 寫入資料庫
public sealed class MyConfig
{
private static readonly Lazy _instance = new(() => new MyConfig());
public static MyConfig Instance => _instance.Value;
// 屬性
public bool LogRenamed { get; set; } = true;
public bool LogChanged { get; set; } = true;
public bool LogCreated { get; set; } = true;
public bool LogDeleted { get; set; } = true;
public string[] WatchedPaths { get; set; } = Array.Empty();
private MyConfig() { }
// 從 SystemConfig 表載入
public void Load()
{
WatchedPaths = Array.Empty();
var paths = new ArrayList();
using (var cmd = Util.DBConnection.CreateCommand())
{
cmd.CommandText = "SELECT ConfigName, ConfigValue FROM SystemConfig";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
string name = reader.GetString(0).Trim().ToLower();
string value = reader.GetString(1);
switch (name)
{
case "logrenamed": LogRenamed = Convert.ToBoolean(value); break;
case "logchanged": LogChanged = Convert.ToBoolean(value); break;
case "logdeleted": LogDeleted = Convert.ToBoolean(value); break;
case "logcreated": LogCreated = Convert.ToBoolean(value); break;
default:
if (name.StartsWith("path")) paths.Add(value.Trim());
break;
}
}
}
}
WatchedPaths = (string[])paths.ToArray(typeof(string));
}
// 存入 SystemConfig 表 (全刪重寫)
public void Save()
{
using (var cmd = Util.DBConnection.CreateCommand())
{
cmd.CommandText = "DELETE FROM SystemConfig";
cmd.ExecuteNonQuery();
cmd.CommandText = "INSERT INTO SystemConfig (ConfigName, ConfigValue) VALUES (?, ?)";
var pName = cmd.CreateParameter(); cmd.Parameters.Add(pName);
var pValue = cmd.CreateParameter(); cmd.Parameters.Add(pValue);
void Set(string n, string v) { pName.Value = n; pValue.Value = v; cmd.ExecuteNonQuery(); }
Set("LogRenamed", LogRenamed.ToString());
Set("LogChanged", LogChanged.ToString());
Set("LogDeleted", LogDeleted.ToString());
Set("LogCreated", LogCreated.ToString());
for (int i = 0; i < WatchedPaths.Length; i++)
{
string p = WatchedPaths[i]?.Trim();
if (!string.IsNullOrEmpty(p)) Set($"path{i}", p);
}
}
}
}
public partial class MyFileSystemWatcherService : ServiceBase
{
private ArrayList _watchers;
private EventLog _eventLog;
public MyFileSystemWatcherService()
{
InitializeComponent();
ServiceName = "MyFileSystemWatcher";
_eventLog = new EventLog { Source = ServiceName };
if (!EventLog.SourceExists(ServiceName))
EventLog.CreateEventSource(ServiceName, "Application");
}
// 核心啟動邏輯
internal bool StartFileSystemWatching()
{
_watchers = new ArrayList();
MyConfig.Instance.Load();
var paths = MyConfig.Instance.WatchedPaths;
var sb = new StringBuilder();
foreach (string path in paths)
{
if (!Path.IsPathRooted(path)) continue;
string basePath, filter;
if (Directory.Exists(path)) { basePath = path; filter = "*.*"; }
else { basePath = Path.GetDirectoryName(path); filter = Path.GetFileName(path); }
if (string.IsNullOrEmpty(basePath) || !Directory.Exists(basePath)) continue;
// 避免監視自身程式目錄 (防止遞迴觸發)
if (basePath.ToUpper().StartsWith(Application.StartupPath.ToUpper())) continue;
if (sb.Length > 0) sb.Append(";");
sb.Append(path);
var watcher = new FileSystemWatcher(basePath, filter)
{
EnableRaisingEvents = true,
IncludeSubdirectories = false // 需遞迴請設 true
};
// 匿名委託綁定事件 (C# 2.0 語法)
if (MyConfig.Instance.LogChanged)
watcher.Changed += (s, a) => WriteLog(a.FullPath, a.ChangeType.ToString());
if (MyConfig.Instance.LogCreated)
watcher.Created += (s, a) => WriteLog(a.FullPath, a.ChangeType.ToString());
if (MyConfig.Instance.LogDeleted)
watcher.Deleted += (s, a) => WriteLog(a.FullPath, a.ChangeType.ToString());
if (MyConfig.Instance.LogRenamed)
watcher.Renamed += (s, a) => WriteLog(a.FullPath, a.ChangeType.ToString());
_watchers.Add(watcher);
}
_eventLog.WriteEntry($"開始監視: {sb}", EventLogEntryType.Information);
return true;
}
private void WriteLog(string objectName, string eventStyle)
{
using (var cmd = Util.DBConnection.CreateCommand())
{
cmd.CommandText = "INSERT INTO FileSystemLog (RecordID, WatchTime, ObjectName, EventStyle) VALUES (?, ?, ?, ?)";
cmd.Parameters.Add(cmd.CreateParameter()).Value = Guid.NewGuid().ToString();
cmd.Parameters.Add(cmd.CreateParameter()).Value = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
cmd.Parameters.Add(cmd.CreateParameter()).Value = objectName;
cmd.Parameters.Add(cmd.CreateParameter()).Value = eventStyle;
cmd.ExecuteNonQuery();
}
}
// ServiceBase 覆寫
protected override void OnStart(string[] args) => StartFileSystemWatching();
protected override void OnStop()
{
if (_watchers != null)
{
foreach (FileSystemWatcher w in _watchers) { w.EnableRaisingEvents = false; w.Dispose(); }
_watchers = null;
}
Util.CloseDBConnection();
base.OnStop();
}
protected override void OnPause()
{
if (_watchers != null)
foreach (FileSystemWatcher w in _watchers) w.EnableRaisingEvents = false;
base.OnPause();
}
protected override void OnContinue()
{
if (_watchers != null)
foreach (FileSystemWatcher w in _watchers) w.EnableRaisingEvents = true;
base.OnContinue();
}
}
internal static class Util
{
private static IDbConnection _conn;
public static IDbConnection DBConnection
{
get
{
if (_conn == null)
{
string dbPath = Path.Combine(Application.StartupPath, "FileSystemWatcher.mdb");
_conn = new OleDbConnection($@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=""{dbPath}""");
_conn.Open();
}
return _conn;
}
}
public static void CloseDBConnection()
{
_conn?.Close();
_conn = null;
}
}
[STAThread]
static void Main(string[] args)
{
try
{
// 定位 InstallUtil.exe (與 mscorlib.dll 同目錄)
string runtimeDir = Path.GetDirectoryName(typeof(string).Assembly.CodeBase.Replace("file:///", ""));
string installUtil = Path.Combine(runtimeDir, "InstallUtil.exe");
foreach (string arg in args)
{
switch (arg.ToLower())
{
case "/install":
Process.Start(installUtil, $"\"{Application.ExecutablePath}\"");
return;
case "/uninstall":
Process.Start(installUtil, $"/u \"{Application.ExecutablePath}\"");
return;
case "/client":
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (var frm = new frmClient())
{
Application.Run(frm);
Util.CloseDBConnection();
}
return;
case "/debug":
// 直接在主執行緒啟動監視,方便 VS 斷點調試 OnStart 邏輯
var svc = new MyFileSystemWatcherService();
svc.StartFileSystemWatching();
Thread.Sleep(Timeout.Infinite); // 阻塞主執行緒
return;
}
}
}
catch (Exception ex) { Console.WriteLine(ex); return; }
// 無參數 → 服務模式
ServiceBase.Run(new MyFileSystemWatcherService());
}
| 操作 | 指令 |
|---|---|
| 安裝服務 | MyWindowsService.exe /install 或 InstallUtil.exe MyWindowsService.exe |
| 解除安裝 | MyWindowsService.exe /uninstall 或 InstallUtil.exe /u MyWindowsService.exe |
| 啟動客戶端 | MyWindowsService.exe /client |
| 除錯服務邏輯 | VS 設定專案啟動參數 /debug,直接 F5 斷點調試 |
| 服務管理器啟動 | 服務 MMC (services.msc) → 「MyFileSystemWatcher」→ 啟動 |
InternalBufferSize = 64*1024;網路磁碟機事件不可靠 → 改用輪詢/USN Journal。本系列完整實現了:
ServiceBase 衍生、FileSystemWatcher 多目錄監視、匿名委託事件綁定、生命週期 (Start/Stop/Pause/Continue) 處理。MyConfig 單例、Access 參數化 SQL、全刪重寫簡化邏輯。ServiceController 非阻塞操作、Timer 輪詢狀態同步 UI、ListView 分組顯示日誌。InstallUtil 自動定位部署。.NET 封裝了底層 SCM (Service Control Manager) 複雜 API,讓開發者專注於 **業務邏輯 (檔案監視、日誌寫入、配置管理)**,以極低成本構建穩健的 Windows 後台服務。