WPF 跟踪更改查看器






4.40/5 (4投票s)
比较两个字符串,并以类似 Word 的跟踪更改格式显示它们。
引言
我当时正在开发一个 WPF 智能客户端应用程序,用户要求能够看到自上次以来在可编辑文本字段中所做的更改(我们为该字段维护一个历史记录表,以便查看所有先前的编辑)。他们希望能够像 Microsoft Word 的修订模式一样查看更改。我在网上搜索,但找不到现成的解决方案。然后我决定自己编写控件来实现此功能。
我知道自己编写类似 Word 的修订模式功能的算法会非常困难。然后我咨询了用户,发现他们都安装了 Microsoft Office 2007。这是个好消息,因为我可以利用 Microsoft Word 的比较功能来实现目标。
背景
基本思路是生成一个 Word 文档,其中包含两个文本 string
的修订模式格式的比较,并以某种方式在 WPF 窗口中显示此文档,同时显示修订标记。
WPF 有一个 DocumentViewer
控件,可以显示 .XPS 或 .PDF 文档。这意味着为了在 WPF 窗口中显示文档,我必须将其转换为 .XPS 或 .PDF,并且要保留修订标记。
以下是将创建此控件的所有步骤
- 创建包含第一个文本字符串的 Word 文档
- 创建包含第二个文本字符串的另一个 Word 文档
- 比较这两个文档以获取修订标记
- 将显示修订标记的 Word 文档转换为 .XPS 或 .PDF 格式
- 使用 WPF
DocumentViewer
显示生成的 .XPS 或 .PDF
Using the Code
要使用此代码,您必须安装以下两种软件
- Microsoft Office 2007
- 2007 Microsoft Office 插件:Microsoft Save as XPS。您可以在 这里 下载。
请按照以下步骤操作以使其正常工作
第 1 步:下载代码。
下载代码并打开项目文件。
第 2 步:添加对所需程序集的引用。
安装上述两项先决条件后,您需要在提供的代码中引用以下程序集。
- 将 Microsoft Word 12.0 Object Library 添加到 Visual Studio 项目中。
- 添加对 ReachFramework.dll 的引用,它将承载 XPS 文档的功能。
注意:要添加这些程序集的引用,请在解决方案资源管理器中右键单击项目名称,选择“添加引用”。在“.NET Framework”下,选择 ReachFramework
和其他程序集,然后单击“确定”按钮。
第 3 步:创建 WPF 窗体
创建一个 WPF 窗体并将其命名为“MainWindow.xaml”。WPF 窗体将包含两个文本框和一个按钮。在单击“比较”按钮时,将比较这两个文本框中的文本,结果将在一个带有 WPF DocumentViewer
的对话框中显示。
WPF 窗体的 XAML 应如下所示
<Window x:Class="WPFTrackChangesTextBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Track Changes Demo" Height="340" Width="464">
<Grid Width="430">
<TextBox Height="100" HorizontalAlignment="Left"
Margin="15,12,0,0" Name="textBox1" VerticalAlignment="Top"
Width="400" TextWrapping="WrapWithOverflow" />
<TextBox Height="100" HorizontalAlignment="Left"
Margin="15,141,0,0" Name="textBox2" VerticalAlignment="Top"
Width="400" TextWrapping="WrapWithOverflow" />
<Button Content="Compare" Height="23" HorizontalAlignment="Left"
Margin="168,255,0,0" Name="btnCompare" VerticalAlignment="Top"
Width="75" Click="btnCompare_Click" />
</Grid>
</Window>
上述 XAML 的输出将如下所示

第 4 步:在 MainWindow.xaml.cs 中导入正确的程序集,如下所示
using System.IO;
using Microsoft.Win32;
using System.Windows.Xps.Packaging;
using Microsoft.Office.Interop.Word;
using Word = Microsoft.Office.Interop.Word;
using System.Reflection;
另外,创建一个全局对象来打开 Word 应用程序。
Word._Application oWord;
第 5 步:在按钮单击事件中,编写以下代码段
private void btnCompare_Click(object sender, RoutedEventArgs e)
{
// get the executable path and set temp file names and path
string path = System.Reflection.Assembly.GetExecutingAssembly().Location;
path = path.Substring(0, path.LastIndexOf(@"\"));
string strDoc1 = path + @"\doc1.docx";
string strDoc2 = path + @"\doc2.docx";
string strXPSDoc = path + @"\Doc.xps";
object oMissing = System.Reflection.Missing.Value;
try
{
// start word application in the background
oWord = new Word.Application();
oWord.Visible = false;
// delete previous occurrences of same files if any
if (File.Exists(strDoc1)) File.Delete(strDoc1);
if (File.Exists(strDoc2)) File.Delete(strDoc2);
if (File.Exists(strXPSDoc)) File.Delete(strXPSDoc);
// Create doc1.docx with text from textBox1
this.CreateDocument(strDoc1, textBox1.Text);
// Create doc2.docx with text from textBox2
this.CreateDocument(strDoc2, textBox2.Text);
// compare doc1.docx with doc2.docx and keep the track
// changes result in doc1.docx
this.DocumentCompare(strDoc1, strDoc2);
// convert doc1.docx to ".XPS" format by keeping the track
// changes markups visible
this.ConvertDocument(strDoc1, strXPSDoc);
// Delete temp word files
File.Delete(strDoc1);
File.Delete(strDoc2);
// Create document viewer dialog box window
System.Windows.Window myWindow = new System.Windows.Window();
DocumentViewer docViewer = new DocumentViewer();
myWindow.Content = docViewer;
// read xps document
XpsDocument xps = new XpsDocument(strXPSDoc, System.IO.FileAccess.Read);
docViewer.Document = xps.GetFixedDocumentSequence();
myWindow.ShowDialog();
// delete the temporary xps file
Uri xpsUri = xps.Uri;
System.IO.Packaging.Package oPackage =
System.IO.Packaging.PackageStore.GetPackage(xpsUri);
oPackage.Close();
System.IO.Packaging.PackageStore.RemovePackage(xps.Uri);
if (File.Exists(strXPSDoc)) File.Delete(strXPSDoc);
}
catch(Exception exp)
{
MessageBox.Show("There was an error!!! " + exp.Message);
}
finally
{
// Quit Word and release the ApplicationClass object.
if (oWord != null)
{
oWord.Quit(ref oMissing, ref oMissing, ref oMissing);
oWord = null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
实例化 Word 应用程序并将其可见性设置为 false
(如上所示)。
// start Word application in the background
oWord = new Word.Application();
oWord.Visible = false;
代码中的注释基本上是自解释的。现在我将解释 btnCompare_Click
事件中调用的以下重要函数
CreateDocument
DocumentCompare
ConvertDocument
请注意,在上述每个函数中,我都会创建一个 Word 文档对象“oDoc
”,如下所示
Word._Document oDoc;
oDoc
将用于创建、打开、比较或保存 Word 文档。在每个函数中使用后关闭并销毁此对象非常重要。
下面是所有 3 个函数的代码片段和说明。
CreateDocument
函数接受两个参数:要创建的文本和文档名称。下面是 CreateDocument
函数的样子。此函数被调用两次,以创建两个 Word 文档,每个文档都包含来自文本框的文本。
private void CreateDocument(string pstrFileName, string pstrText)
{
object oMissing = System.Reflection.Missing.Value;
object oEndOfDoc = "\\endofdoc"; /* \endofdoc is a predefined bookmark */
Word._Document oDoc;
oDoc = null;
try
{
oDoc = oWord.Documents.Add
(ref oMissing, ref oMissing, ref oMissing, ref oMissing);
//Insert a paragraph at the beginning of the document.
Word.Paragraph oPara1;
oPara1 = oDoc.Content.Paragraphs.Add(ref oMissing);
oPara1.Range.Text = pstrText;
object filename = pstrFileName;
oDoc.SaveAs(pstrFileName, ref oMissing, ref oMissing, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing,
ref oMissing, ref oMissing);
}
catch (Exception e)
{
throw e;
}
finally
{
// Close and release the Document object.
if (oDoc != null)
{
oDoc.Close(ref oMissing, ref oMissing, ref oMissing);
oDoc = null;
}
}
}
DocumentCompare
函数接受两个要比较的文件名,并将比较结果保存在第一个文档中。它看起来像
private void DocumentCompare(string pstrDoc1, string pstrDoc2)
{
object oMissing = System.Reflection.Missing.Value;
//create a readonly variable of object type and assign it to false.
object readonlyobj = false;
object filename = pstrDoc1;
//create a word document object and open the above file..
Word._Document oDoc;
oDoc = null;
try
{
oDoc = oWord.Documents.Open(ref filename, ref oMissing, ref readonlyobj,
ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing);
string filenm = pstrDoc2;
object compareTarget =
Microsoft.Office.Interop.Word.WdCompareTarget.wdCompareTargetCurrent;//This will
//keep the result of comparison in the first word document.
object addToRecentFiles = false;
oDoc.Compare(filenm, ref oMissing, ref compareTarget, ref oMissing,
ref oMissing, ref addToRecentFiles, ref oMissing, ref oMissing);
}
catch (Exception e)
{
throw e;
}
finally
{
// Close and release the Document object.
if (oDoc != null)
{
oDoc.Close(ref oMissing, ref oMissing, ref oMissing);
oDoc = null;
}
}
}
ConvertDocument
会将传递给它的文档转换为 .xps 格式,同时保留修订标记可见。它看起来像这样
public void ConvertDocument(string sourceDocPath, string targetFilePath)
{
// Make sure the source document exists.
if (!System.IO.File.Exists(sourceDocPath))
throw new Exception("The specified source document does not exist.");
Word._Document oDoc;
oDoc = null;
// Declare variables for the Documents.Open and
// ApplicationClass.Quit method parameters.
object paramSourceDocPath = sourceDocPath;
//object oMissing = Type.Missing;
object oMissing = System.Reflection.Missing.Value;
// Declare variables for the Document.ExportAsFixedFormat method parameters.
string paramExportFilePath = targetFilePath;
WdExportFormat paramExportFormat = WdExportFormat.wdExportFormatXPS;
bool paramOpenAfterExport = false;
WdExportOptimizeFor paramExportOptimizeFor =
WdExportOptimizeFor.wdExportOptimizeForOnScreen;
WdExportRange paramExportRange = WdExportRange.wdExportAllDocument;
int paramStartPage = 0;
int paramEndPage = 0;
WdExportItem paramExportItem = WdExportItem.wdExportDocumentWithMarkup; //This is
//the key to keep track changes markup;
bool paramIncludeDocProps = true;
bool paramKeepIRM = true;
WdExportCreateBookmarks paramCreateBookmarks =
WdExportCreateBookmarks.wdExportCreateWordBookmarks;
bool paramDocStructureTags = true;
bool paramBitmapMissingFonts = true;
bool paramUseISO19005_1 = false;
try
{
// Open the source document.
oDoc = oWord.Documents.Open(ref paramSourceDocPath, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing,
ref oMissing, ref oMissing, ref oMissing, ref oMissing);
// Export it in the specified format.
if (oDoc != null)
oDoc.ExportAsFixedFormat(paramExportFilePath, paramExportFormat,
paramOpenAfterExport, paramExportOptimizeFor,
paramExportRange, paramStartPage, paramEndPage, paramExportItem,
paramIncludeDocProps, paramKeepIRM, paramCreateBookmarks,
paramDocStructureTags, paramBitmapMissingFonts,
paramUseISO19005_1, ref oMissing);
}
catch (Exception e)
{
throw e;
}
finally
{
// Close and release the Document object.
if (oDoc != null)
{
oDoc.Close(ref oMissing, ref oMissing, ref oMissing);
oDoc = null;
}
}
}
上述代码中的以下行确保生成的“.xps”文件包含修订标记。
WdExportItem paramExportItem = WdExportItem.wdExportDocumentWithMarkup;
最后,使用以下代码段打开一个带有 DocumentViewer
的对话框,并打开 xps
文档
// Create document viewer dialog box window
System.Windows.Window myWindow = new System.Windows.Window();
DocumentViewer docViewer = new DocumentViewer();
myWindow.Content = docViewer;
// read xps document
XpsDocument xps = new XpsDocument(strXPSDoc, System.IO.FileAccess.Read);
docViewer.Document = xps.GetFixedDocumentSequence();
myWindow.ShowDialog();
另外,请确保在 finally
块中关闭 Word 应用程序,如下所示
if (oWord != null)
{
oWord.Quit(ref oMissing, ref oMissing, ref oMissing);
oWord = null;
}
第 6 步:运行应用程序。它的外观如下

当您单击“比较”按钮时,将触发 btnCompare_Click
事件,您将看到以下结果

关注点
在清理过程中,我发现一个有趣的事情是,我无法删除“Doc.xps”文件,因为它被进程锁定。为了使文件名下次可用,我不得不使用以下代码将其删除。
// delete the temporary xps file
Uri xpsUri = xps.Uri;
System.IO.Packaging.Package oPackage =
System.IO.Packaging.PackageStore.GetPackage(xpsUri);
oPackage.Close();
System.IO.Packaging.PackageStore.RemovePackage(xps.Uri);
if (File.Exists(strXPSDoc)) File.Delete(strXPSDoc);
历史
- 2011 年 5 月 9 日:初始版本