65.9K
CodeProject 正在变化。 阅读更多。
Home

PowerPoint 模板生成器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2014 年 8 月 7 日

CPOL

17分钟阅读

viewsIcon

21658

downloadIcon

370

本文介绍了一个 Windows 窗体应用程序,该应用程序自动化 MS Office PowerPoint 2007,以用您的输入替换模板中的预定义文本

引言

您是否需要准备多个 PowerPoint 演示文稿?您是否厌倦了总是要修改所有幻灯片和母版中重复的内容?也许本文中描述的应用程序能给您带来一些帮助。

借助这个应用程序,您可以个性化每一个 PowerPoint 2007 模板文件。

技术

此外,这个小巧而简单的应用程序包含了几种可能对您有用的技术。本文将向您展示如何将这些技术整合在一篇文章中并加以使用。

以下列表展示了所实现的技术及其简要描述:

  • 自动化 Microsoft PowerPoint
    借助程序集 Microsoft.Office.Interop.PowerPointOffice,可以实现 PowerPoint 自动化,以便加载 PowerPoint 模板,并替换整个 PowerPoint 演示文稿中几乎所有部分的某些文本。PowerPoint 的自动化部分被分离到一个独立的项目中。该项目有一个清晰的文档化接口,因此重用该项目会非常容易。
  • 使用 EventHandler 取消长时间运行的方法调用
    为了能够以相对细粒度的方式取消替换工作流,我们使用了 EventHandler。通过一个接口方法,可以从 PowerPointServiceWrapper 对象内部获取 EventHandler。如果从外部调用该 EventHandler,可以设置一个内部标志,这样 PowerPointServiceWrapper 对象就知道已发送了取消请求。在当前细粒度任务完成后,替换工作流将被中断,方法调用将立即返回。
  • 向调用者报告进度
    以相对细粒度的方式报告进度的问题与取消主题非常相似。但在这里,我们必须反向思考。现在,我们必须将信息从 PowerPointServiceWrapper 对象内部传递给调用者。在当前情况下,我们必须将进度信息传递给 BackgroundWorkerReportProgress 方法。这里的解决方案是将 ReportProgress 方法放入 PowerPointServiceWrapper 对象中。这可以通过将 ReportProgress 方法赋给一个类型为 System.Action<int, object> 的内部成员来实现。
  • BackgroundWorker
    替换工作流在一个 BackgroundWorker 中运行。这将通过提供一个可用的进度条、取消工作流的可能性以及在替换工作流运行时窗口不会冻结来增强用户体验。
  • 从 Windows 资源管理器拖放文件到用户界面
    可以通过从 Windows 资源管理器拖放到窗口的方式来选择 PowerPoint 模板文件。本文将向您展示拖放功能的使用方法。
  • 带有状态信息和进度条的 StatusStrip
    StatusStrip 控件通常用于显示状态信息。在此应用程序中,它用于在替换工作流期间显示进度条和状态信息(当前正在处理工作流的哪一步)。StatusStrip 控件的使用非常简单,没有什么特别之处。因此,这里仅列出,不作深入解释。
  • 使用 UserSettings 在应用程序启动时恢复数据
    为了让用户不必每次都输入所有的替换信息,文本框在应用程序启动时会从恢复的用户设置中填充,这些设置是在上一次替换工作流期间存储的。
  • 获取当前用户的信息
    当您第一次启动应用程序时,替换信息会用默认值填充。用户名、电话号码和电子邮件地址将从系统中获取。为此,将使用 System.DirectoryServices.AccountManagement.UserPrincipal.Current
  • 创建和使用 UserControl
    正如您从附带的屏幕截图中看到的,用户界面有很多行,每行都包含以下控件:一个复选框用于决定是否使用该替换信息,一个标签帮助用户将每个替换信息与 PowerPoint 模板中的正确位置对应起来,以及两个包含替换信息本身的文本框。每一行都封装在一个 UserControl 中,可以轻松地放置到窗体上。

以上列表中的技术将在稍后更详细地解释。

工作原理

如果您下载附加的 zip 文件,您会找到一个 TestTemplate.potx 文件。在 TestTemplate.potx 文件中,您可以看到诸如 SET_YOUR_NAME_HERESET_TITLE_HERE 等词语,这些词语将在最终显示的 PowerPoint 演示文稿的所有幻灯片、母版、布局中被您的输入所替换。例如,在这种情况下,SET_YOUR_NAME_HERE 这个词将被替换为 roli.hof。这些占位符可以根据您的需要进行更改和调整。

该程序的工作方式是简单的文本替换。这将使您能够完全灵活地满足您的需求。标签仅用于结构上的辅助。借助每行前面的复选框,您可以分别启用或禁用每个替换配置。

当您按下 Create Presentation 按钮时,您在 UI 中所做的输入将被保存到用户设置中。因此,大多数输入您只需进行一次。下次启动应用程序时,您上一次运行的输入将从用户设置中恢复。

应用程序有一个 Close 按钮。在替换工作流期间,该按钮的标签会变为 Cancel。通过这个按钮,您可以关闭窗口,或者如果替换工作流正在运行,您可以取消替换工作流。

进度显示在窗口底部的 StatusStrip 中。

PowerPoint 模板文件可以通过 OpenFileDialog 或从文件资源管理器中 拖放 来选择。

测试环境

该程序在安装了 MS Office 2007 的 WIN7 x64 系统上进行了测试。开发环境使用 Visual Studio 2010。

背景

如前所述,与 PowerPoint 的交互位于一个单独的项目中。因此,使该应用程序也适用于其他 MS Office 版本将很容易。甚至可以适用于完全不同的文档类型。为此,您只需实现一个类似于 PowerPointServiceWrapper 类并实现了 IPowerPointServiceWrapper 接口的类。PowerPointServiceWrapper 具备向调用者报告进度以及调用者能够触发取消事件来取消替换作业的功能。

使用代码

如果您下载 zip 文件,除了 TestTemplate.potx 文件外,您还会找到两个 Visual Studio 2010 项目。一个是 PowerPointServiceWrapper,它封装了与 PowerPoint 应用程序的交互。另一个是 PowerpointTemplateGenerator,其中实现了用户界面。以下部分将详细介绍 PowerPointServiceWrapper 项目。稍后我们将看一下应用程序本身。

自动化 Microsoft PowerPoint

PowerPointServiceWrapper 项目的类图

IPowerPointServiceWrapper 接口
以下代码块展示了 IPowerPointServiceWrapper 接口。每个方法的功能在其代码注释中都有描述。

using System;
using System.Collections.Generic;

namespace PowerPointService
{
  public interface IPowerPointServiceWrapper
  {
    /// <summary>
    /// Initializes the progress reporter.
    /// Call this method with the progress reporter method as parameter if you need to handle
    /// progress information.
    /// </summary>
    /// <param name="progressReporter">The progress reporter.</param>
    void InitializeProgressReporter(Action<int, object> progressReporter);
 
    /// <summary>
    /// Initializes the cancelation event handler.
    /// Call this method with an EventHandler as parameter if you need to react on cancelation events.
    /// </summary>
    /// <param name="cancelationEventHandler">The cancelation event handler.</param>
    void InitializeCancelationEventHandler(out EventHandler cancelationEventHandler);
 
    /// <summary>
    /// Cancelations the operation asynchronous.
    /// This method will be called internally if the initialized cancelationEventHandler will be called.
    /// This method is listed in the interface just that it will be not forgotten in case that someone
    /// will implement that interface for another usage than in this example.
    /// Normally there is no need to call This method manually.
    /// </summary>
    /// <param name="sender">The sender.
    /// Not needed.</param>
    /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.
    /// Not needed</param>
    void CancelationAsync(object sender, EventArgs e);
 
    /// <summary>
    /// Runs the replacements.
    /// </summary>
    /// <param name="templateFileLocation">The template file location.</param>
    /// <param name="replaceInfoList">The replace information list.</param>
    /// <returns>true if everything was ok.</returns>
    bool RunReplacements(string templateFileLocation, List<ReplaceInfo> replaceInfoList);
  }
}

初始化取消处理和进度报告
方法 InitializeProgressReporterInitializeCancelationEventHandler 必须在方法 RunReplacements 之前调用。如果您不需要取消功能或进度报告器,那么 RunReplacements 方法在没有这些功能的情况下也能工作。为此,只需不调用这两个初始化方法即可。

RunReplacements 方法
主要的替换工作流在 RunReplacements 方法中实现。让我们来看看它。

public bool RunReplacements(string templateFileLocation, List<ReplaceInfo> replaceInfoList)
{
  try
  {
    ProgressChanged(0, "Loading template ...");
    LoadPPTemplate(templateFileLocation);
    if (IsCancelationCalled(10, "Loading template finnished ..."))
    {
      return false;
    }

    ProgressChanged(11, "Processing Slides - Master pages ...");
    ReplaceInMasterPages(replaceInfoList);
    if (IsCancelationCalled(20, "Processing Slides - Master pages finnished ..."))
    {
      return false;
    }

    ProgressChanged(21, "Processing SlideMaster - Custom Layout ...");
    ReplaceInCustomLayout(replaceInfoList);
    if (IsCancelationCalled(40, "Processing SlideMaster - Custom Layout finnished ..."))
    {
      return false;
    }

    ProgressChanged(41, "Processing Slides ...");
    ReplaceInSlides(replaceInfoList);
    if (IsCancelationCalled(80, "Processing Slides finnished ..."))
    {
      return false;
    }

    ProgressChanged(81, "Processing TitleMaster ...");
    ReplaceInTitleMaster(replaceInfoList);
    if (IsCancelationCalled(90, "Processing TitleMaster finnished ..."))
    {
      return false;
    }

    ProgressChanged(91, "Processing SlideMaster ...");
    ReplaceInSlideMaster(replaceInfoList);
    if (IsCancelationCalled(99, "Processing SlideMaster finnished ..."))
    {
      return false;
    }

    MakePowerPointVisible(true);
    if (IsCancelationCalled(100, "Generation Finished!"))
    {
      return false;
    }

    return true;
  }

  catch (Exception ex)
  {
    ProgressChanged(100, "Generation with error: " + ex.Message);
    return false;
  }
}

取消处理和进度报告
RunReplacements 方法中,有几个内部方法调用来完成替换工作。在每次调用之前,我们都会看到处理进度信息的代码。因此,会调用 ProgressChanged 方法,在该方法中会检查进度报告器是否已初始化,如果已初始化,进度信息将被传递给调用者。每次调用之后,我们都会检查是否应取消替换工作流,并再次处理进度信息。这两个操作都在 IsCancelationCalled 方法中处理。

加载 PowerPoint 模板
RunReplacements 方法中,进行替换工作的第一件事是从输入参数 templateFileLocation 指定的位置加载 PowerPoint 模板文件。在加载 PowerPoint 模板文件之前,必须初始化 PowerPoint 应用程序并启动 PowerPoint。所有这些活动都将在 LoadPPTemplate 方法中完成。以下代码块展示了 LoadPPTemplate 方法。

private void LoadPPTemplate(string templateFilePath)
{
  app = new PP.Application();
  app.Visible = OFFICE.MsoTriState.msoTrue;
  presentaions = app.Presentations;
  presentation = presentaions.Open(templateFilePath, OFFICE.MsoTriState.msoTrue, OFFICE.MsoTriState.msoTrue, app.Visible);
}

执行替换
PowerPoint 对象模型(请参阅 PowerPoint object model reference at http://msdn.microsoft.com/en-us/library/bb251394(v=office.12).aspx))使用所谓的 Shapes 来管理文本和其他对象。在 MSDN 参考中,您可以使用链接 http://msdn.microsoft.com/en-us/library/bb265915(v=office.12).aspx,它将向您展示如何使用形状。如果我们想在不同的幻灯片、布局、母版等中更改文本,我们首先必须从所有不同的幻灯片、布局、母版等中获取形状,并查找每个包含我们想要替换的文本的形状。replaceInfoList 包含了应该被替换的文本以及应该写入的文本。在一个循环中,将搜索并可能替换所有的替换信息。这当然不是最快的解决方案。但我决定采用这种方式,因为它很容易进行调整,也容易编写可重用的代码。以下代码块展示了 ReplaceInTitleMaster 方法和 ReplaceInShapes 方法。RunReplacements 方法的其他方法调用与 ReplaceInTitleMaster 方法非常相似。因此这里不再列出。

private void ReplaceInTitleMaster(List<ReplaceInfo> replaceInfoList)
{
  if (presentation.HasTitleMaster == OFFICE.MsoTriState.msoTrue)
  {
    foreach (PP.Shape shape in presentation.TitleMaster.Shapes)
    {
      ReplaceInShapes(shape, replaceInfoList);
    }
  }
}

private void ReplaceInShapes(PP.Shape shape, List<ReplaceInfo> replaceInfoList)
{
  if (replaceInfoList != null)
  {
    foreach (ReplaceInfo replaceInfo in replaceInfoList)
    {
      if (IsCancelationCalled())
      {
        return;
      }
      if (shape.TextFrame.HasText == OFFICE.MsoTriState.msoTrue)
      {
        if (shape.TextFrame.TextRange.Text.Contains(replaceInfo.TextToReplace))
        {
          string text = shape.TextFrame.TextRange.Text;
          text = text.Replace(replaceInfo.TextToReplace, replaceInfo.TextReplaceWith);
          shape.TextFrame.TextRange.Text = text;
        }
      }
    }
  }
}

IPowerPointServiceWrapper 接口的使用
下一个代码块显示了后台工作线程的 DoWork 方法。在这里您可以看到 IPowerPointServiceWrapper 的用法。首先构建 replaceInfoList,然后分配一个新的 PowerpointServiceWrapper,接着初始化进度报告器和取消处理,然后通过调用 RunReplacement 方法来执行替换工作流。

private void BackgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
  System.ComponentModel.BackgroundWorker worker = sender as System.ComponentModel.BackgroundWorker;
  List<ReplaceInfo> replaceInfoList = FillReplaceInfoList();
  IPowerPointServiceWrapper pp = new PowerPointServiceWrapper();
  pp.InitializeProgressReporter(worker.ReportProgress);
  pp.InitializeCancelationEventHandler(out CancelationEvent);

  // bool return value not used in this case!
  bool isOk = pp.RunReplacements(txtTemplateFile.Text, replaceInfoList);
 
  if (worker.CancellationPending == true)
  {
    e.Cancel = true;
    return;
  }
}

replaceInfoList 是一个 ReplaceInfo 类型的 List。您可以在下一个代码块中看到这个简单的类。

namespace PowerPointService
{
  public class ReplaceInfo
  {
    public string TextToReplace { get; set; }
    public string TextReplaceWith { get; set; }
  }
}

使用 EventHandler 取消长时间运行的方法调用

BackgroundWorker 对取消操作有内置支持。在 DoWork 方法中,您可以查询工作线程是否被请求取消。但在我们的案例中,我们将长时间运行的工作流封装在一个单独的项目中,而有意义支持取消的工作流位于一个单独的方法(RunReplacements 方法)内。因此,在这种情况下,我们不能使用 BackgroundWorker 的内置机制。有必要将取消支持引入 PowerPointServiceWrapper 内部。在这个应用程序中,这是借助 EventHandler 解决的。关于 EventHandler 的一般信息可以在 MSDN 库中找到(参见 http://msdn.microsoft.com/en-us/library/system.eventhandler(v=vs.100).aspx)。在我们的案例中,IPowerPointServiceWrapper 接口有一个名为 InitializeCancelationEventHandler 的方法,它有一个类型为 EventHandler 的 out 参数。通过调用 InitializeCancelationEventHandler 方法,将分配一个新的 EventHandler 并将其赋值/附加到 out 参数 cancelationEventHandler 上。如果有什么东西从外部调用这个 EventHandlerPowerPointServiceWrapper 内部附加的方法将被调用。在这个方法中,会设置一个标志,以便我们可以在替换工作流期间查询取消请求是否已经被触发。下面的代码块将展示其实现。

PowerPointServiceWrapper 内部

布尔成员初始设置为 false。

private bool IsCancelationPending = false;

public void InitializeCancelationEventHandler(out EventHandler cancelationEventHandler)
{
  cancelationEventHandler = new EventHandler(CancelationAsync);
}

PowerPointServiceWrapper 内部实现 EventHandler 方法。

public void CancelationAsync(object sender, EventArgs e)
{
  IsCancelationPending = true;
}

以下代码块展示了检查取消是否已被触发的方法。如果已被触发,进度将被设置为 100%,并且该方法将返回 true,以便调用方法可以取消并返回给调用者。该方法的使用可以在 ReplaceInShapes 方法中看到。

private bool IsCancelationCalled()
{
  if (IsCancelationPending == true)
  {
    ProgressChanged(100, "Generation aborted by user!");
  }
  return IsCancelationPending;
}

调用者的 EventHandler 相关实现。

在这种情况下,调用者同义于使用 IPowerpointServiceWrapper 的 Windows Forms 应用程序。首先,将一个 EventHandler 类型的成员添加到您的调用者中,如下面的代码所示。

private event EventHandler CancelationEvent;

然后调用 InitializeCancelationEventHandler 方法,并将上面的 CancelationEvent 成员作为参数传入。现在唯一缺少的就是在需要触发取消时调用 CancelationEvent 方法。在这种情况下,我们将这个取消调用放入取消按钮的点击事件中,如下面的代码块所示。

private void btnCancel_Click(object sender, EventArgs e)
{
  if (backgroundWorker.IsBusy)
  {
    if (backgroundWorker.WorkerSupportsCancellation == true)
    {
      toolStripStatusLabel1.Text = "Generation aborting ... please wait!";
      backgroundWorker.CancelAsync();
      if (CancelationEvent != null)
      {
        CancelationEvent(sender, e);
      }
      btnCancel.Enabled = false;
    }
  }
  else
  {
    this.Close();
  }
}

向调用者报告进度

为了向用户报告进度,我们面临着与我们已经讨论过的取消请求类似的情况。BackgroundWorker 已经内置了报告进度信息的功能,但是因为我们只在 BackgroundWorkerDoWork 方法中调用一个长时间运行的方法,所以我们不能直接使用这个功能。我们必须做些什么来将这个功能引入 PowerPointServiceWrapper 内部。在我们的例子中,我们将使用 System 命名空间中的 Action 类(参见 http://msdn.microsoft.com/en-us/library/bb549311(v=vs.100).aspx)。借助它,BackgroundWorker 对象的 ReportProgress 方法可以作为 IPowerPointServiceWrapper 接口方法的参数,从而将 ReportProgress 方法传入 PowerPointServiceWrapper 内部。这样就可以在 PowerPointServiceWrapper 的内部实现中调用该方法。接下来,我们将看看相关的实现部分。

PowerPointServiceWrapper 内部

首先声明一个类型为 Action<int, object> 的成员 (ReportProgressChangedDelegate)。然后在调用接口方法 (InitializeProgressReporter) 时,将 BackgroundWorker 对象的 ReportProgress 方法赋给新声明的成员。然后就可以调用这个成员了。在这种情况下,它被封装在一个单独的方法 (ProgressChanged) 中。检查 ReportProgressChangedDelegate 是否不为 null 使得即使没有初始化进度报告器,程序也能运行。

private Action<int, object> ReportProgressChangedDelegate;

public void InitializeProgressReporter(Action<int, object> progressReporter)
{
  ReportProgressChangedDelegate = progressReporter;
}

private void ProgressChanged(int percent, string userState)
{
  if (ReportProgressChangedDelegate != null)
  {
    ReportProgressChangedDelegate(percent, userState);
  }
}

调用者的进度相关实现。

在这种情况下,调用者同义于使用 IPowerpointServiceWrapper 的 Windows Forms 应用程序。首先,我们必须将事件处理程序附加到 BackgroundWorker 对象的 ProgressChanged 事件上。

private void InitializeBackgroundWorker()
{
  backgroundWorker = new System.ComponentModel.BackgroundWorker();
  backgroundWorker.WorkerReportsProgress = true;
  backgroundWorker.WorkerSupportsCancellation = true;
  backgroundWorker.DoWork += new System.ComponentModel.DoWorkEventHandler(BackgroundWorker_DoWork);
  backgroundWorker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(BackgroundWorker_ProgressChanged);
  backgroundWorker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(BackgroundWorker_RunWorkerCompleted);
}

然后我们必须实现新附加的方法 (BackgroundWorker_ProgressChanged)。

private void BackgroundWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
  toolStripStatusLabel1.Text = e.UserState.ToString();
  if (e.ProgressPercentage == 0)
  {
    toolStripProgressBar1.Maximum = 100;
  }
  toolStripProgressBar1.Value = e.ProgressPercentage;
}

然后我们必须调用 IPowerPointServiceWrapperInitializeProgressReporter 方法,将 BackgroundWorker 对象的 ReportProgress 方法传入 PowerPointServiceWrapper 对象中。这是在 BackgroundWorker_DoWork 方法中完成的(参见上面几行的代码片段)。

pp.InitializeProgressReporter(worker.ReportProgress);

BackgroundWorker

BackgroundWorker 在 MSDN 库中有很好的文档记录(参见 http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker(v=vs.100).aspx)。该文档还包含如何使用 BackgroundWorker 的示例。因此,您在这里只会找到一个粗略的核对清单,说明您需要做什么。

  • 首先分配一个新的 Backgroundworker
  • 附加 EventHandlers (DoWork, ProgressChanged, RunWorkerCompleted)
  • 如果您想使用进度报告,请将 WorkerReportsProgress 设置为 true
  • 如果您需要取消的可能性,请将 WorkerSupportsCancellation 设置为 true
  • 实现 EventHandlers 中定义的方法
  • 调用 BackgroundWorker 对象的 RunWorkerAsync 方法

从 Windows 资源管理器拖放文件到用户界面

在这个应用程序中,可以从 Windows 资源管理器中拖放一个 PowerPoint 模板文件到可以插入模板文件位置的文本框中。要使用拖放功能,我们需要做几件事。有关拖放功能的更多信息可以在以下链接中找到 http://support.microsoft.com/kb/307966/en。首先,我们必须将文本框的 AllowDrop 属性更改为 true。然后我们必须附加两个事件。一个是 DragEnter,另一个是 DragDrop 事件。然后必须实现这两个事件的方法。当文件被拖动到文本框的边界内时,会发生 DragEnter 事件。在这里,我们告诉应用程序这是一个复制过程。这会导致鼠标光标变为显示一个加号以表示复制。

private void txtTemplateFile_DragEnter(object sender, DragEventArgs e)
{
  e.Effect = DragDropEffects.Copy;
}

当我们松开鼠标按钮时,会发生 DragDrop 事件。在这里,我们检查 DragEventArgs 的 Data 成员。然后我们查看此数据中是否能找到 FileDropList,如果能找到,我们就取第一个条目并将其赋给文本框。请参见下面的代码。

private void txtTemplateFile_DragDrop(object sender, DragEventArgs e)
{
  DataObject data = (DataObject)e.Data;
  if (data.ContainsFileDropList())
  {
    string[] rawFiles = (string[])e.Data.GetData(DataFormats.FileDrop);
    if (rawFiles != null)
    {
      txtTemplateFile.Text = rawFiles[0];
      Properties.Settings.Default.TemplateFile = txtTemplateFile.Text;
      Properties.Settings.Default.Save();
      txtTemplateFile.Refresh();
    }
  }
}

使用 UserSettings 在应用程序启动时恢复数据

在 MSDN 库中,我们可以在以下链接找到有关应用程序设置和用户设置的信息 http://msdn.microsoft.com/en-us/library/0zszyc6e(v=vs.100).aspx。在此应用程序中,用户设置用于在应用程序启动时恢复上次运行的用户输入。这样用户就不必总是从头开始输入所有内容。在 Visual Studio 2010 中定义 UserSettings 的一种方法是使用项目的属性页。在这里您可以找到 Settings 选项卡,您可以在其中插入新设置并定义数据类型和范围。在我们的情况下,我们对所有设置使用 string 数据类型和 User 范围。在窗体的构造函数中,我们恢复上一个会话的设置,或者用默认值初始化它们(用于在您的系统上首次启动应用程序)。可以通过 Properties.Settings.Default 访问设置。请参见下面的代码。

public Form1()
{
  InitializeComponent();

  InitializeBackgroundWorker();

  Properties.Settings set = Properties.Settings.Default;

  InitializeSettingsIfNotAvailable(set);

  InitializeReplaceTextControls(set);
  txtTemplateFile.Text = set.TemplateFile;
}

private static void InitializeSettingsIfNotAvailable(Properties.Settings set)
{
  if (set.NameReplace == null || set.NameReplace.Length <= 0)
  {
    .
    .
    .
    set.TitleReplace = "SET_TITLE_HERE";
    set.Title = "Title";
    .
    .
    .
    set.Save();
  }
}

private void InitializeReplaceTextControls(Properties.Settings set)
{
  .
  .
  .
  SetControl(rtcTitle, "Title:", set.TitleReplace, set.Title, true);
  .
  .
  .
}

设置主要在替换工作流启动时保存。要存储设置,我们必须调用 Properties.Settings.DefaultSave 方法。请参见下面的代码。

private void btnCreatePresentation_Click(object sender, EventArgs e)
{
  if (txtTemplateFile.Text != null && txtTemplateFile.Text.Length > 0)
  {
    Progress("Generation started!", 0);
    ButtonsEnabled(false);
    SaveProperties();
    .
    .
    .
}

private void SaveProperties()
{
  Properties.Settings set = Properties.Settings.Default;
  .
  .
  .
  set.Title = rtcTitle.TextReplaceWithThat;
  set.TitleReplace = rtcTitle.TextToReplace;
  .
  .
  .
  set.Save();
}

获取当前用户的信息

InitializeSettingsIfNotAvailable 方法中,将从系统中获取当前登录用户的一些信息,并用作默认值。这仅在应用程序首次在系统上启动时发生。为此,将使用 System.DirectoryServices.AccountManagement 命名空间。要使用该命名空间,您必须添加相应的引用。在此命名空间中,我们可以使用 UserPrincipal.Current 从当前用户获取所需的数据。更多信息可以在 http://msdn.microsoft.com/en-us/library/system.directoryservices.accountmanagement.userprincipal.current(v=vs.100).aspx 找到。以下代码向您展示了获取用户信息的用法。

private static void InitializeSettingsIfNotAvailable(Properties.Settings set)
{
  if (set.NameReplace == null || set.NameReplace.Length <= 0)
  {
    AM    AM.UserPrincipal user = null;
    try
    {
      user = AM.UserPrincipal.Current;
    }
    catch (Exception)
    {
      // If the query will not work, then do nothing. The user has to insert the values manually.
    }

    set.NameReplace = "SET_YOUR_NAME_HERE";
    if (user != null && user.GivenName != null && user.Surname != null)
    {
      set.Name = user.GivenName + " " + user.Surname;
    }
    .
    .
    .
    set.PhoneReplace = "SET_YOUR_PHONENUMBER_HERE";
    if (user != null && user.VoiceTelephoneNumber != null)
    {
      set.Phone = user.VoiceTelephoneNumber;
    }

    set.EmailReplace = "SET_YOUR_EMAIL_HERE";
    if (user != null && user.EmailAddress != null)
    {
      set.Email = user.EmailAddress;
    }
    .
    .
    .
  }
}

创建和使用 UserControl

用于定义哪个文本应被哪个其他文本替换的 UI 是通过使用一个复选框、一个标签和两个文本框来实现的。当然,解决这个问题的方法还有很多。但是这个应用程序是从一个非常简单的应用程序发展而来的,我没有在这个主题上改变 UI 的架构。但很早就清楚,这种包含四个控件的相同结构将不止一次被需要,所以我决定将这四个控件封装在一个可重用的 UserControl 中。在 MSDN 库中,链接 http://msdn.microsoft.com/en-us/library/aa302342.aspx 提供了一些关于如何创建 UserControl 的信息。但在这篇文章中,实现语言是 Visual Basic。因此,我将为您提供一个额外的链接 http://www.akadia.com/services/dotnet_user_controls.html。在我看来,这篇文章是一个很好且易于理解的起点。

要创建一个 UserControl,请在 Visual Studio 的解决方案资源管理器中右键单击您想要放置新 UserControl 的项目。现在查找 Add,然后选择 User Control...。Visual Studio 将为您创建所有内容,以便您可以开始将控件添加到面板上。现在设计您的 UserControl - 添加所有需要的控件并定义您的 UserControl 的布局。现在查看您的 UserControl 的代码,并为 UserControl 添加属性(带有 getter 和 setter)。通过这些属性,您现在可以从外部访问 UserControl 内部的控件。请看下面这个应用程序中使用的用户控件的代码。

using System.Windows.Forms;

namespace PowerpointTemplateGenerator
{
  public partial class ReplaceTextControl : UserControl
  {
    public bool IsActive
    {
      get { return this.chkActivated.Checked; }
      set { this.chkActivated.Checked = value; }
    }
    public string ControlName
    {
      get { return this.lblParameterName.Text; }
      set { this.lblParameterName.Text = value; }
    }
    public string TextToReplace
    {
      get { return this.txtReplaceThat.Text; }
      set { this.txtReplaceThat.Text = value; }
    }
    public string TextReplaceWithThat
    {
      get { return this.txtNew.Text; }
      set { this.txtNew.Text = value; }
    }
    public ReplaceTextControl()
    {
      InitializeComponent();
    }
  }
}

在主窗体中,对 UserControl 的访问被封装在两个方法中。请看下面的代码。

private void SetControl(ReplaceTextControl rtc, string labelName, string textToReplace, string replaceWithThat, bool isActive)
{
  rtc.ControlName = labelName;
  rtc.TextToReplace = textToReplace;
  rtc.TextReplaceWithThat = replaceWithThat;
  rtc.IsActive = isActive;
}

private List<ReplaceInfo> FillReplaceInfoList()
{
  List<ReplaceInfo> replaceInfoList = new List<ReplaceInfo>();
  foreach (Control control in this.Controls)
  {
    if (control is ReplaceTextControl)
    {
      ReplaceTextControl rtc = control as ReplaceTextControl;

      if (rtc.IsActive)
      {
        ReplaceInfo ri = new ReplaceInfo();
        ri.TextToReplace = rtc.TextToReplace;
        ri.TextReplaceWith = rtc.TextReplaceWithThat;
        replaceInfoList.Add(ri);
      }
    }
  }
  return replaceInfoList;
}

关注点

解决问题总是有多种方法。所附的解决方案是从一个非常简单的方法发展而来的。例如,在最开始,整个实现都位于 Form1 类中,没有 BackgroundWorker,没有进度信息,也没有任何取消的可能性。但我发现,用这种糟糕的编码方式,没有任何东西是可重用的,所以我花了一些时间来让它变得更好一些。现在,如果我需要实现一个简单的工具,我可以将所附的应用程序作为一个很好的起点,从中复制我需要的部分。因此,为使其更具可重用性而付出的那一点额外努力很快就会得到回报。

历史

2014年8月5日 – 首次发表文章
2014年8月7日 – 更新文章以增加细节深度

© . All rights reserved.