# 六、创建用户界面来配置服务

## 概述

在这本书里，已经提到了 Windows 服务没有接口，配置文件是控制服务行为的正确方式。通常，这个配置文件可以在文本编辑器中编写并保存到磁盘上，但是如果为此目的构建一个具有用户界面的程序，它看起来会更专业。许多已知的服务(如文件服务器)使这些程序对用户可用，以便以直观和简单的方式创建或修改其自己的配置文件。

本章旨在构建一个名为 MonitorServiceGUI 的 Windows 窗体程序，该程序将处理创建和编辑先前构建的服务所需的配置文件。

## 在 Visual Studio 中创建解决方案

第一步是使用 Visual Studio 创建一个名为 **MonitorServiceGUI** 的 Windows 窗体项目。要使用的编程语言将是 C# 并且目标框架将是。NET 4。

![](img/image015.jpg)

图 11: MonitorServiceGUI 项目对话框

当 Visual Studio 结束项目创建时，在解决方案资源管理器的树中可以找到两个名为 **Form1.cs** 和 **Program.cs** 的文件。为清楚起见，这些文件将分别重命名为 **mainform.cs** 和 **mainprogram.cs** 。现在，解决方案资源管理器将如下图所示。

![](img/image016.jpg)

图 MonitorServiceGUI 项目的解决方案资源管理器

**mainprogram.cs** 文件包含应用程序的入口点，由一个名为`mainprogram`的静态类处理。这个类有一个名为`Main`的方法，它设置应用环境，创建一个`mainform`类的实例(在 **mainform.cs** 中定义)，并在屏幕上显示。可以在下面的代码示例中看到。

代码示例 25

```cs
          using System;
          using System.Collections.Generic;
          using System.Linq;
          using System.Windows.Forms;

          namespace monitorservicegui
          {
              static class mainprogram
              {
                  /// <summary>
                  /// The main entry point for the application.
                  /// </summary>
                  [STAThread]
                  static void Main()
                  {
                      Application.EnableVisualStyles();
                      Application.SetCompatibleTextRenderingDefault(false);
                      Application.Run(new mainform());
                  }
              }
          }

```

`mainform`将是项目中唯一的表单。这将包含所有 GUI 元素，以便设置服务行为参数，并将处理服务运行所需的配置文件的创建。

## 设置项目的主窗体

主窗体只需要几个图形元素就能完成它的目的。这些要素是:

*   一个组合框，用于选择将在一周中的哪一天进行备份。此元素的`DropDownStyle`属性必须设置为`DropDownList` ``.`` 使用设计器，项目列表将填充所有星期几的名称。这份名单的第一个名字将是【T4 日报】。
*   位于组合框左侧的标签，带有图例*星期几*
*   用于输入备份完成时间的屏蔽文本框
*   放置在屏蔽文本框左侧的标签，带有图例*小时*
*   文本框，用于输入要备份的文件的路径
*   放置在前一个文本框左侧的标签，带有图例*源路径*
*   文本框，用于输入压缩备份的存储路径
*   放置在前一个文本框左侧的标签，带有图例*目标路径*
*   一个保存按钮，用于将参数存储在一个 XML 文件中
*   “取消”按钮关闭表单并忽略可能进行的更改

除了前面提到的元素，还需要设置以下属性来完成`mainform`设计:

*   `Text`–该属性将被设置为“备份服务接口”
*   `FormBorderStyle`–该属性将被设置为固定单个。
*   `MaximizeBox`–该属性最好设置为假。
*   `CancelButton`–该属性将被设置为前面提到的取消按钮图形元素。

当所有图形元素都放置在表单中，并且设置了前面提到的属性值时，`mainform`的设计器视图将如下图所示。

![](img/image017.jpg)

图 13:主窗体. cs 设计器视图

## 查找以前的 XML 参数文件

首先要做的是检查一个 XML 参数文件是否已经存在。如果是这样，存储在文件中的所有值都需要传递给表单中的图形元素，以便向用户显示它们。如果文件不存在，程序必须将一组初始值传递给表单中的图形元素并显示它们。为此，我们将使用`mainform`的`Load`事件，如下面的代码示例所示。

代码示例 26

```cs
          using System;
          using System.Collections.Generic;
          using System.ComponentModel;
          using System.Data;
          using System.Drawing;
          using System.IO;
          using System.Linq;
          using System.Text;
          using System.Windows.Forms;
          using System.Xml;

          namespace monitorservicegui
          {
              public partial class mainform : Form
              {
                  string HomeDir = Path.GetDirectoryName(Application.ExecutablePath).Trim();
                  public mainform()
                  {
                      InitializeComponent();
                  }

                  private void mainform_Load(object sender, EventArgs e)
                  {
                      if (!this.check_parameters())
                      {
                          this.comboBox1.SelectedIndex = 0;
                          this.comboBox1.Refresh();
                          this.maskedTextBox1.Text = "00:00";
                          this.maskedTextBox1.Refresh();
                          this.textBox1.Text = "";
                          this.textBox1.Refresh();
                          this.textBox2.Text = "";
                          this.textBox2.Refresh();
                      }
                  }

                  private Boolean check_parameters()
                  {
                      Boolean result = default(Boolean);

                      if (!System.IO.Directory.Exists(this.HomeDir + "\\parameters"))
                      {
                          System.IO.Directory.CreateDirectory(this.HomeDir + "\\parameters");
                          result = false;
                      }
                      else
                      {
                          if (System.IO.File.Exists(this.HomeDir + "\\parameters\\srvparams.xml"))
                          {
                              result = true;
                              XmlDocument parametersdoc = new XmlDocument();

                              try
                              {
                                  parametersdoc.Load(this.HomeDir + "\\parameters\\srvparams.xml");
                              }
                              catch
                              {
                                  result = false;

                              }

                              if (result)
                              {
                                  XmlNode BackupParameters = parametersdoc.ChildNodes.Item(1).ChildNodes.Item(0);
                                  this.textBox1.Text = BackupParameters.Attributes.GetNamedItem("source").Value.Trim();
                                  this.textBox1.Refresh();
                                  this.textBox2.Text = BackupParameters.Attributes.GetNamedItem("destination").Value.Trim();
                                  this.textBox2.Refresh();
                                  this.comboBox1.SelectedIndex = Convert.ToInt32(BackupParameters.Attributes.GetNamedItem("dayofweek").Value.Trim());
                                  this.comboBox1.Refresh();
                                  this.maskedTextBox1.Text = BackupParameters.Attributes.GetNamedItem("hour").Value.Trim();
                                  this.maskedTextBox1.Refresh();
                              }

                              parametersdoc = null;
                          }
                          else
                          {
                              result = false;
                          }
                      }

                      return (result);
                  }

              }
          }

```

创建了一个名为`check_parameters()`的单独方法，目的是查询 XML 文件的存在性。如果文件存在，该方法使用`XmlDocument`对象来解析文件并获取所有参数值。然后，这些值被传递给表单中相应的图形元素。返回`true`值，表示找到了文件，所有参数都已正确加载。否则，如果找不到或无法解析该 XML 文件，则返回`false`值，表示参数值不可用。

`Load`事件检查从`check_parameters()`方法返回的值。如果返回一个值`false`，所有的图形元素都用初始值填充并刷新以向用户显示这些值。

## 处理数据录入

此时，备份服务的用户界面会检查 XML 文件是否存在，如果该文件存在，则会加载参数值。

现在，是时候控制数据输入，以防止在 XML 文件中存储错误的值，这会导致服务故障。必须执行以下任务:

*   避免时间值超出 00:00 到 23:59 的范围。
*   验证源路径和目标路径是否存在。

`DayofWeek`的值由`combobox`自动控制，因为`SelectedIndex`属性值会在 0 到 7 之间，取决于用户选择的是哪一天，包括*每天*选项。

### 验证时间值

对于时间值验证，您将使用`maskedTextbox`控件的`TypeValidationCompleted`事件。下面的代码示例显示了这是如何实现的。

代码示例 27

```cs
          private void maskedTextBox1_TypeValidationCompleted(object sender, TypeValidationEventArgs e)
          {
              if (!e.IsValidInput)
              {
                  e.Cancel = true;
              }
          }

```

该方法检查属于传递的`TypeValidationEventArgs`参数的`IsValidInpu` *t* 属性的值。如果该值为假，则参数的`Cancel`属性设置为`true`。这避免了将焦点传递给窗体中的另一个控件，包括“取消”按钮和“关闭”按钮。

### 检查源路径和目标路径的存在

源路径和目标路径必须存在于磁盘中，以确保服务能够正常工作。`mainform`的设计有两个文本框，可以在其中输入这些路径。为了验证这些文件夹的存在，将使用两个路径的`Validating`事件，如下面的代码片段所示。

代码示例 28

```cs
          private void textBox1_Validating(object sender, CancelEventArgs e)
          {
              if (this.textBox1.Text.Trim().Length == 0)
              {
                  e.Cancel = true;
              }
              else
              { 
                if (!System.IO.Directory.Exists(this.textBox1.Text.Trim()))
                 {
                    MessageBox.Show("The Source Path entered doesn't exist.", "Backup Service Interface");
                    e.Cancel = true;
                 }
              }
          }

          private void textBox2_Validating(object sender, CancelEventArgs e)
          {
              if (this.textBox2.Text.Trim().Length == 0)
              {
                  e.Cancel = true;
              }
              else
              {
                  if (!System.IO.Directory.Exists(this.textBox2.Text.Trim()))
                  {
                      MessageBox.Show("The Destination Path entered doesn't exist.", "Backup Service Interface");
                      e.Cancel = true;
                  }
              }

          }

```

对于这两种方法，如果文本框中没有输入，该方法将`true`存储在`CancelEventArgs`参数的`Cancel`属性中。这可以防止文本框失去焦点，并且光标保持在其中。否则，该方法检查文本框中的条目是否对应于系统中的有效路径。如果路径不存在，该方法会显示一个错误消息对话框，并将`CancelEventArgs`参数的`Cancel`属性设置为`true`值，以确保光标保持在文本框中。

## 将参数保存在 XML 文件中

输入所有参数值后，最后一步是将这些值存储在服务将用来正常工作的 XML 文件中。要成功，需要完成以下任务列表:

*   创建一个具有适当属性的`XmlDocument`对象来保存参数值。
*   使用`XmlDocument`的`Save`方法存储文件。
*   告诉服务参数已被更改，以使服务改变其行为。

### 使用 XmlDocument 对象

将创建一个名为`Save_Parameters`的方法来执行 XML 文件创建。该方法将从`Save`按钮的`Click`事件中调用，如下面的代码示例所示。

代码示例 29

```cs
          private void button1_Click(object sender, EventArgs e)
          {
              this.Save_Parameters();
          }

          private void Save_Parameters()
          {
              XmlDocument oparamsxml = new XmlDocument();

              XmlProcessingInstruction _xml_header = oparamsxml.CreateProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");

              oparamsxml.InsertBefore(_xml_header, oparamsxml.ChildNodes.Item(0));

              XmlNode parameters = oparamsxml.CreateNode(XmlNodeType.Element, "Parameters", "");
              XmlNode backup = oparamsxml.CreateNode(XmlNodeType.Element, "Backup", "");

              XmlAttribute attribute = oparamsxml.CreateAttribute("source");
              attribute.Value = this.textBox1.Text.Trim();
              backup.Attributes.Append(attribute);

              attribute = oparamsxml.CreateAttribute("destination");
              attribute.Value = this.textBox2.Text.Trim();
              backup.Attributes.Append(attribute);

              attribute = oparamsxml.CreateAttribute("dayofweek");
              attribute.Value = this.comboBox1.SelectedIndex.ToString("00");
              backup.Attributes.Append(attribute);

              attribute = oparamsxml.CreateAttribute("hour");
              attribute.Value = this.maskedTextBox1.Text.Trim();
              backup.Attributes.Append(attribute);

              parameters.AppendChild(backup);
              oparamsxml.AppendChild(parameters);

              if (!Directory.Exists(this.HomeDir + "\\parameters"))
              {
                  Directory.CreateDirectory(this.HomeDir + "\\parameters");
              }

              oparamsxml.Save(this.HomeDir + "\\parameters\\srvparams.xml");
          }

```

首先，该方法创建一个`XmlDocument`对象和`Parameters`根节点。然后，创建`Backup`子节点及其所有属性。每个属性对应于服务工作所需的一个参数，它的正确值取自为此目的放置在表单中的图形元素。

最后，`XmlDocument`对象的`Save`方法将文件存储在磁盘中。

### 通知服务参数已更改

将参数保存在磁盘中的动作意味着服务行为需要改变。要通知服务，程序需要执行以下任务:

*   检查目标系统中是否安装了该服务。
*   停止服务的执行。
*   开始执行服务，以便让它加载新的参数。

将创建一个名为`Notify_Changes`的方法来执行前面的任务，并且将从`Save`按钮的`Click`事件中调用该方法，就在前一节讨论的`Save_Parameters`方法调用之后。现在，代码将如下所示。

代码示例 30

```cs
          private void button1_Click(object sender, EventArgs e)
          {
              this.Save_Parameters();
              this.Notify_Changes();
              this.Close();
          }

          private void Save_Parameters()
          {
              XmlDocument oparamsxml = new XmlDocument();

              XmlProcessingInstruction _xml_header = oparamsxml.CreateProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");

              oparamsxml.InsertBefore(_xml_header, oparamsxml.ChildNodes.Item(0));

              XmlNode parameters = oparamsxml.CreateNode(XmlNodeType.Element, "Parameters", "");
              XmlNode backup = oparamsxml.CreateNode(XmlNodeType.Element, "Backup", "");

              XmlAttribute attribute = oparamsxml.CreateAttribute("source");
              attribute.Value = this.textBox1.Text.Trim();
              backup.Attributes.Append(attribute);

              attribute = oparamsxml.CreateAttribute("destination");
              attribute.Value = this.textBox2.Text.Trim();
              backup.Attributes.Append(attribute);

              attribute = oparamsxml.CreateAttribute("dayofweek");
              attribute.Value = this.comboBox1.SelectedIndex.ToString("00");
              backup.Attributes.Append(attribute);

              attribute = oparamsxml.CreateAttribute("hour");
              attribute.Value = this.maskedTextBox1.Text.Trim();
              backup.Attributes.Append(attribute);

              parameters.AppendChild(backup);
              oparamsxml.AppendChild(parameters);

              if (!Directory.Exists(this.HomeDir + "\\parameters"))
              {
                  Directory.CreateDirectory(this.HomeDir + "\\parameters");
              }

              oparamsxml.Save(this.HomeDir + "\\parameters\\srvparams.xml");
          }

          private void Notify_Changes()
          {

              ServiceController controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "MonitorService");

              if (controller!=null) //The service is installed
              {
                 if (controller.Status == ServiceControllerStatus.Running) //The service is running, so it needs to be stopped and started again to reload the parameters
                 {
                     controller.Stop(); //Stops the service
                     controller.WaitForStatus(ServiceControllerStatus.Stopped); //Waits until the service is really stopped
                     controller.Start(); //Starts the service and reload the parameters
                 }
              }

          }

```

### 服务控制器类

一个`ServiceController` 组件允许我们访问和管理运行在机器上的 Windows 服务。`ServiceController`类可用于连接和控制现有服务的行为。当`ServiceController` 类的一个实例被创建时，它的属性可以被设置为与一个特定的窗口服务交互。该类可用于启动、停止和操作服务。

创建`ServiceController`实例后，必须在其中设置两个属性来标识与之交互的服务:计算机名和您想要控制的服务名。

| ![](img/note.png) | 注意:默认情况下，MachineName 设置为本地计算机，因此您不需要更改它，除非您想将实例设置为指向另一台计算机。 |

### 增加一个*系统。服务流程*参考

一个`ServiceController`代表一个窗口服务，在`System.ServiceProcess`命名空间中定义。在导入该命名空间之前，必须添加对`System.ServiceProcess`程序集的引用。

要添加对程序集的引用，请在 Visual Studio 中右键单击项目名称并选择**添加引用**，然后浏览需要添加到应用程序中的程序集。

**![ServiceControllerImg1.jpg](img/image019.jpg)**

图 14:使用系统添加引用对话框。ServiceProcess 程序集

### *通知 _ 变更*代码做什么？

`ServiceController.GetServices`静态方法返回计算机上运行的所有服务的列表。除了这种方法之外，`Enumerable.FirstOrDefault`关联方法用于扫描整个列表，寻找名为`MonitorService`的服务。如果找不到名称，`FirstOrDefault`返回空值；否则，它将返回一个与`MonitorService`关联的`ServiceController`对象的实例。

如果返回与`MonitorService`相关联的实例，程序将查询存储在该实例的`Status`属性中的值。可以存储在属性中的可能值有:

*   `ServiceControllerStatus.ContinuePending`–服务继续挂起。这对应于 Win32 ``SERVICE_CONTINUE_PENDING`` 常量，定义为 0x00000005。
*   `ServiceControllerStatus.Paused`–服务暂停。这对应于 Win32 ``SERVICE_PAUSED`` 常量，定义为 0x00000007。
*   `ServiceControllerStatus.PausePending`–服务暂停正在等待。这对应于 Win32 ``SERVICE_PAUSE_PENDING`` 常量，定义为 0x00000006。
*   `ServiceControllerStatus.Running`–服务正在运行。这对应于 Win32 ``SERVICE_RUNNING`` 常量，定义为 0x00000004。
*   `ServiceControllerStatus.StartPending`–服务正在启动。这对应于 Win32 SERVICE_START_PENDING 常量，定义为 0x00000002。
*   `ServiceControllerStatus.Stopped`–服务没有运行。这对应于 Win32 SERVICE_STOPPED 常量，定义为 0x00000001。
*   `ServiceControllerStatus.StopPending`–服务正在停止。这对应于 Win32 SERVICE_STOP_PENDING 常量，定义为 0x00000003。

在这种情况下，只有当`Status`的值为`ServiceControllerStatus.Running`时，该方法才需要执行动作。这意味着服务当前正在计算机中运行，需要停止才能重新加载参数。`Stop`方法用于执行该动作。

#### 确保服务真正停止

要再次启动服务执行，程序需要确保它确实停止了。服务停止所消耗的时间取决于它有多少依赖项。`WaitForStatus`方法用于延迟程序执行，直到服务达到`Stopped`状态。现在，程序确定服务没有运行。

#### 正在重新加载服务参数

由于每次服务开始执行时都会读取参数文件，因此一旦服务停止，程序就会执行`ServiceController`实例的`Start`方法。此操作会导致从 XML 文件中重新加载参数。

## 整个代码

现在， **mainform.cs** 代码看起来像下面的示例。

代码示例 31

```cs
          using System;
          using System.Collections.Generic;
          using System.ComponentModel;
          using System.Data;
          using System.Drawing;
          using System.IO;
          using System.Linq;
          using System.Text;
          using System.Windows.Forms;
          using System.ServiceProcess;
          using System.Xml;

          namespace monitorservicegui
          {
              public partial class mainform : Form
              {
                  string HomeDir = Path.GetDirectoryName(Application.ExecutablePath).Trim();
                  public mainform()
                  {
                      InitializeComponent();
                  }

                  private void mainform_Load(object sender, EventArgs e)
                  {
                      if (!this.check_parameters())
                      {
                          this.comboBox1.SelectedIndex = 0;
                          this.comboBox1.Refresh();
                          this.maskedTextBox1.Text = "00:00";
                          this.maskedTextBox1.Refresh();
                          this.textBox1.Text = "";
                          this.textBox1.Refresh();
                          this.textBox2.Text = "";
                          this.textBox2.Refresh();
                      }
                  }

                  private Boolean check_parameters()
                  {
                      Boolean result = default(Boolean);

                      if (!System.IO.Directory.Exists(this.HomeDir + "\\parameters"))
                      {
                          System.IO.Directory.CreateDirectory(this.HomeDir + "\\parameters");
                          result = false;
                      }
                      else
                      {
                          if (System.IO.File.Exists(this.HomeDir + "\\parameters\\srvparams.xml"))
                          {
                              result = true;
                              XmlDocument parametersdoc = new XmlDocument();

                              try
                              {
                                  parametersdoc.Load(this.HomeDir + "\\parameters\\srvparams.xml");
                              }
                              catch
                              {
                                  result = false;

                              }

                              if (result)
                              {
                                  XmlNode BackupParameters = parametersdoc.ChildNodes.Item(1).ChildNodes.Item(0);
                                  this.textBox1.Text = BackupParameters.Attributes.GetNamedItem("source").Value.Trim();
                                  this.textBox1.Refresh();
                                  this.textBox2.Text = BackupParameters.Attributes.GetNamedItem("destination").Value.Trim();
                                  this.textBox2.Refresh();
                                  this.comboBox1.SelectedIndex = Convert.ToInt32(BackupParameters.Attributes.GetNamedItem("dayofweek").Value.Trim());
                                  this.comboBox1.Refresh();
                                  this.maskedTextBox1.Text = BackupParameters.Attributes.GetNamedItem("hour").Value.Trim();
                                  this.maskedTextBox1.Refresh();
                              }

                              parametersdoc = null;
                          }
                          else
                          {
                              result = false;
                          }
                      }

                      return (result);
                  }

                  private void button2_Click(object sender, EventArgs e)
                  {
                      this.Close();
                  }

                  private void maskedTextBox1_TypeValidationCompleted(object sender, TypeValidationEventArgs e)
                  {
                      if (!e.IsValidInput)
                      {
                          e.Cancel = true;
                      }
                  }

                  private void textBox1_Validating(object sender, CancelEventArgs e)
                  {
                      if (this.textBox1.Text.Trim().Length == 0)
                      {
                          e.Cancel = true;
                      }
                      else
                      { 
                        if (!System.IO.Directory.Exists(this.textBox1.Text.Trim()))
                         {
                            MessageBox.Show("The Source Path entered doesn't exist.", "Backup Service Interface");
                            e.Cancel = true;
                         }
                      }
                  }

                  private void textBox2_Validating(object sender, CancelEventArgs e)
                  {
                      if (this.textBox2.Text.Trim().Length == 0)
                      {
                          e.Cancel = true;
                      }
                      else
                      {
                          if (!System.IO.Directory.Exists(this.textBox2.Text.Trim()))
                          {
                              MessageBox.Show("The Destination Path entered doesn't exist.", "Backup Service Interface");
                              e.Cancel = true;
                          }
                      }

                  }

                  private void button1_Click(object sender, EventArgs e)
                  {
                      this.Save_Parameters();
                      this.Notify_Changes();
                      this.Close();
                  }

                  private void Save_Parameters()
                  {
                      XmlDocument oparamsxml = new XmlDocument();

                      XmlProcessingInstruction _xml_header = oparamsxml.CreateProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");

                      oparamsxml.InsertBefore(_xml_header, oparamsxml.ChildNodes.Item(0));

                      XmlNode parameters = oparamsxml.CreateNode(XmlNodeType.Element, "Parameters", "");
                      XmlNode backup = oparamsxml.CreateNode(XmlNodeType.Element, "Backup", "");

                      XmlAttribute attribute = oparamsxml.CreateAttribute("source");
                      attribute.Value = this.textBox1.Text.Trim();
                      backup.Attributes.Append(attribute);

                      attribute = oparamsxml.CreateAttribute("destination");
                      attribute.Value = this.textBox2.Text.Trim();
                      backup.Attributes.Append(attribute);

                      attribute = oparamsxml.CreateAttribute("dayofweek");
                      attribute.Value = this.comboBox1.SelectedIndex.ToString("00");
                      backup.Attributes.Append(attribute);

                      attribute = oparamsxml.CreateAttribute("hour");
                      attribute.Value = this.maskedTextBox1.Text.Trim();
                      backup.Attributes.Append(attribute);

                      parameters.AppendChild(backup);
                      oparamsxml.AppendChild(parameters);

                      if (!Directory.Exists(this.HomeDir + "\\parameters"))
                      {
                          Directory.CreateDirectory(this.HomeDir + "\\parameters");
                      }

                      oparamsxml.Save(this.HomeDir + "\\parameters\\srvparams.xml");
                  }

                  private void Notify_Changes()
                  {

                      ServiceController controller = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == "MonitorService");

                      if (controller!=null) //The service is installed
                      {
                         if (controller.Status == ServiceControllerStatus.Running) //The service is running, so it needs to be stopped and started again to reload the parameters
                         {
                             controller.Stop(); //Stops the service
                             controller.WaitForStatus(ServiceControllerStatus.Stopped); //Waits until the service is really stopped
                             controller.Start(); //Starts the service and reload the parameters
                         }
                      }

                  }

              }
          }

```

现在项目已经准备好构建可执行文件了。对于一个人们只是在阅读而不一定在代码编辑器中跟随的环境，下面的截图显示了运行完成的程序。

![](img/image020.png)

图 15:充满数据的备份服务界面主窗体

![](img/image021.png)

图 16:备份服务界面主窗体，显示所有周和周末的列表

![](img/image022.png)

图 17:输入了错误源路径的备份服务接口

## 部署用户界面可执行文件

最后，用户界面项目只包含一个可执行文件。它可以与服务可执行文件分发包一起部署，并且必须复制到安装服务可执行文件的同一文件夹中。分发包必须包括以下文件:

*   Installutil.exe(随 Visual Studio 一起提供)
*   Monitorservice.exe(服务可执行文件)
*   Monitorservicegui.exe(服务用户界面可执行文件)
*   Ionic.Zip.dll(用于创建 ZIP 的库)
*   带有服务执行参数的 XML 文件
*   BAT 安装文件
*   BAT 卸载文件

## 章节总结

由于窗口服务没有接口，因此配置文件是控制其行为的合适方式。文本编辑器可以用来创建或编辑这个文件，但是更专业的做法是为程序提供一个用户界面来处理配置文件。此外，这是完成这项任务的更简单的方法。

本章解释了如何创建一个 Windows 窗体程序，它将为这里讨论的`MonitorService`提供用户界面。该程序只有一个表单，在该表单中将输入所有需要的参数值。

该程序将参数值写入一个 XML 文件，该文件将由服务可执行程序使用。如果该 XML 文件以前存在于计算机中，程序将能够读取参数值。

最后，程序将与服务通信，以便在参数值改变时通知它。将使用`ServiceController`类来完成此任务。为了使用`ServiceControlle` *r* 类，需要在项目中添加对`System.ServiceProcess`组件的引用。

`ServiceController`类允许程序与安装在计算机中的任何服务进行通信和控制。使用此类，可以停止、暂停或启动服务。该程序可以查询特定服务的存在，以及该服务当前是否正在运行或停止。

创建可执行文件后，必须将其与服务分发包一起部署，并复制到目标计算机上运行服务的文件夹中。