如果你的程式需要系統沒有登入時就執行,也希望避免被誤關,就需要寫Windows Service.本篇介紹除錯,資源檔和如何在系統欄做一個狀態程式去控制你的服務.
接上一篇建立Windows Service上卷,現在我們來看如何加入設定,資源檔和控制服務.
設定檔
App.config是.NET 1.0就有的設定檔,其實一直都.NET3.5都還在用,只是加入了.settings和其讀取類別更換成ConfigurationManager.在這裡先解釋一下App.config和.settings的差別在那裡.如果有適當的權限,兩者都是可以透過程式去做存取的.其實.settings是由Visual Studio幫你在App.config建立SectionGroup和Section,並且把相關的程式碼都設定好.而App.config寫入則需要自己動手處理,也是可以建立自訂的SectionGroup和Section.所以.settings其實是Visual Studio提供的一個工具.
自訂Section會放在另一篇講解.這裡會解釋一下.settings.其實每個application會有預設的一個.settings.在方案總管->專案->Right Click選屬性,然後在主頁選擇設定,點中間的建立設定檔就可以.當然你也可以在專案Right Click加入其他.settings
接下來看到的畫面,名稱就是該設定值的名稱,型別是值的強型別,值就是設定值.這裡最重要的就是範圍,如果範圍設定成Application,那麼同一台電腦上的不同使用者,就會使用相同的這個設定值,而且不能用程式修改.如果範圍是User的話,就可以用程式修改值,並且每個使用者有自己的設定,這個部份的.config會放在每個使用者的Application Data資料夾裡.所以如果要修改所有User的設定值時,就要對每位使用者一個一個.config的改,又或者按左上方的[同步處理].
要使用該設定值時就用下面的程式碼,其他.settings也是用類似的方法
//讀取 ProjectName.Properties.Settings.Default.SettingName; //寫入 ProjectName.Properties.Settings.Default.SettingName = xxx; ProjectName.Properties.Settings.Default.Save();
資源檔
通常我會用.resx去放一些整個程式會共用到的字串,也可以用來做多國語言,這樣修改時才不會造成只有部份有改到的情況,在專案Right Click加入一個資源檔.資源檔基本上只是一個key-value的對應.讀取時就用下面的程式碼
ResourceManager manager = new ResourceManager("Namespace.ResxFileName", Assembly.GetExecutingAssembly()); manager.GetString("key"); 小結 設定檔和資源檔都包含在安裝專案的內容檔裡,不用另外設定,如果有多國語言的話,可能要在安裝專案加入輸出時一併加入.
控制程式
接下來就是本篇的主餐,要寫另一個程式來對該服務做控制,因為常用的服務通常不會打開控制台來設定這麼麻煩,而且這裡還會用IPC去要求服務把供一些其他資訊.
基本上要達成這些的需求: 啟動時在System Tray顯示圖示,可以直接控制服務,Double Click要有介面對服務下指令,按[X]時會回到System Tray.
需要元件: ServiceController, NotifyIcon, ImageList, ContextMenuStrip, MenuStrip
ServiceController是用來控制服務的,要把ServiceName設定成你想控制的服務的名稱.
NotifyIcon是用來在System Tray右邊顯示小圖示的,但不用設定,我會把圖片集中在ImageList,然後在程式裡設定.
ImageList是用來存放圖片資源的,分別用在NotifyIcon和Menu等.
ContextMenuStrip讓使用者在小圖示上Right Click就可以控制服務
MenuStrip在顯示介面的模式提供跟ContextMenuStrip同樣的功能
1. 最小化
先把NotifyIcon的Visible設成True,加入NotifyIcon_DoubleClick的處理方法
private void nicSystemTray_DoubleClick(object sender, EventArgs e) { ShowFormMain(); } private void ShowFormMain() { this.Show(); this.WindowState = FormWindowState.Normal; //this.ShowInTaskbar = true; }
這裡有一個重點,千萬不能用ShowInTaskbar = true,因為這個值是唯讀的,但不知道為何compile會過,記得不能用.接下來把Form的WindowState設定成Minimized,並加入Form_Resize和Form_Closing的處理方法
private void FormMain_Resize(object sender, EventArgs e) { if (this.WindowState == FormWindowState.Minimized) { this.Hide(); //ShowInTaskbar is read-only //this.ShowInTaskbar = false; } } //從介面模式按[x]會縮小到System Tray,在縮小模式才真的Close private void FormMain_FormClosing(object sender, FormClosingEventArgs e) { if (this.WindowState != FormWindowState.Minimized) { e.Cancel = true; this.Hide(); this.WindowState = FormWindowState.Minimized; } } private void FormMain_FormClosed(object sender, FormClosedEventArgs e) { if (nicSystemTray != null) { nicSystemTray.Visible = false; nicSystemTray.Dispose(); nicSystemTray = null; } }
這個FormClosed的方法是用來處理程式已關閉可是小圖示還是留在System Tray的奇怪現像的.
2. 控制服務
加入開始,停止,關閉三個Item到MenuStrip和ContextMenuStrip,使用相同的Click處理方法.我這裡用同步方式去控制Service,你可以用非同步的.
private void startToolStripMenuItem_Click(object sender, EventArgs e) { try { TimeSpan l_timeout = TimeSpan.FromMilliseconds(30 * 1000); svcReadLogger.Start(); svcReadLogger.WaitForStatus(System.ServiceProcess.ServiceControllerStatus.Running, l_timeout); } catch { MessageBox.Show("服務無法啟動,請檢查相關設定!"); } svcReadLogger.Refresh(); UpdateContextMenuStatus(svcReadLogger.Status); } private void stopToolStripMenuItem_Click(object sender, EventArgs e) { try { TimeSpan l_timeout = TimeSpan.FromMilliseconds(60 * 1000); if (svcReadLogger.Status != System.ServiceProcess.ServiceControllerStatus.Stopped && svcReadLogger.Status != System.ServiceProcess.ServiceControllerStatus.StopPending) { svcReadLogger.Stop(); svcReadLogger.WaitForStatus(System.ServiceProcess.ServiceControllerStatus.Stopped, l_timeout); } } catch { MessageBox.Show("服務無法停用,建議您可以先利用「工作管理員」將 Service1.exe 程序結束,再進行解除安裝。"); } svcReadLogger.Refresh(); UpdateContextMenuStatus(svcReadLogger.Status); }
這裡的svcReadLogger就是我的ServiceController物件.先定好一個timeout的TimeSpan,Windows預設是30秒,我們這裡也用30秒,如果等了30秒後服務沒有啟動成功,就會放棄.WaitForStatus會用同步方式等到服務啟動成功為止.UpdateContextMenuStatus方法是用來更新圖示的,下面會再詳細講.停止服務基本上跟啟動服務一模一樣.
3. 圖示
對不同服務狀態顯示不同圖示和Disable不同Menu Item.先在ImageList的集合加入一些小圖示.然後我每次變更服務狀態都會呼叫一次UpdateContextMenuStatus,你也可以用Event來做
private void UpdateContextMenuStatus(ServiceControllerStatus status) { switch (status) { case ServiceControllerStatus.Running: nicSystemTray.Icon = Icon.FromHandle(((Bitmap)imlTrayIcon.Images[0]).GetHicon()); 功能ToolStripMenuItem.DropDownItems[0].Enabled = cmsShortCutMenu.Items[0].Enabled = false; 功能ToolStripMenuItem.DropDownItems[1].Enabled = cmsShortCutMenu.Items[1].Enabled = true; break; case ServiceControllerStatus.Stopped: nicSystemTray.Icon = Icon.FromHandle(((Bitmap)imlTrayIcon.Images[1]).GetHicon()); 功能ToolStripMenuItem.DropDownItems[0].Enabled = cmsShortCutMenu.Items[0].Enabled = true; 功能ToolStripMenuItem.DropDownItems[1].Enabled = cmsShortCutMenu.Items[1].Enabled = false; break; default: //error break; } }
這裡用Icon.FromHandle()方法從ImageList取得圖示.MenuItem[0]為啟動服務的選項,MenuItem[1]為停止服務的選項.最後加入一個ContextMenuStrip的MenuItem為小圖示Right Click時選退出才會真正的退出服務控制程式.
private void exitToolStripMenuItem_Click(object sender, EventArgs e) { this.WindowState = FormWindowState.Minimized; this.Close(); }
4. 捷徑
一般安裝程式都會加入程式捷徑,用上一篇介紹的方式建立好安裝專案,最後在主要輸出的設計畫面,選擇使用者桌面或使用者程式功能表,或者其他你自建的資料夾,在空白的地方Right Click->建立新捷徑,記得這裡是選目標程式,所以要選應用程式資料夾,選主要輸出,按確定就可以了.
總結
程式很簡單但加上圖示後效果還不錯的.大家可以試一試.
已停止
已啟用
控制服務