Back in February 2008, I showed how to create a wiki-like help system.
Since 2008, I have implemented that feature in most applications because the user can really customize the documentation to fit their needs. The system has proven to be useful in many occasion.
Not only users are more than happy to be able create and improve the help by themselves, they have also asked for some improvements. These required are the same as those listed in the “Extending this system” section of the 2008 article. I have seen them coming! One of the improvements is the ability to be able to jump from one topic to another (this is why I have a listbox control on the left of the screen). Lately, a customer also asked me to generate a Word document containing all the topics from the wiki system.
That last request will be subject of this article.
Downloadable demo
This month downloadable demo contains both VB and C#. The solution has been created using Visual Studio 2015 but the same code can be used in earlier version as well.
Figure 1: The demo application in action
No Office automation here
I won’t use Office automation here. I will use a library I have been using for many years now. I will use Aspose.Words for .Net which is a commercial (not free) library.
It is true that I could have achieve the same results using Office Automation but I had so many bad experiences trying to automate Office applications (mostly Excel) that I have stopped trying a long time ago. Most issues were coming from the various versions to support, the users trying to work on documents at the same time my application was trying to work with them, tools not available on a server, and so on.
You can download a trial version from the link here above. The required DLL is also included in the zip. The trial is not time-bombed or feature limited. Instead, you will see a big red message in the generated document.
Code reused from the 2008 article
I have reused some code from the article published in 2008. But I have also simplified the demo. It is now a single standalone form. It will be easier for you to reuse. If you want to see how to integrate it as part of a larger application, refer to 2008 article.
So I have reused the same toolbar and the same RichTextBox control and the code related to them.
Persisting the wiki
For the simplicity of this article, I didn’t want to have a database. I have preferred to keep the mechanism I was using in the demo of 2008: a dataset persisted in a .XML document. Easy enough for the requirements of the demo application.
That means that when the application starts, the OpenHelpDataset method is called. This method will load a file named Help.xml from the current directory into an in-memory dataset. If the file does not exist, a dataset with the correct structure is created. The Dataset is then bound to the listbox to let the user jumps from one topic to another. The code is:
Private Sub OpenHelpDataset() If Not File.Exists("Help.xml") Then mdataset = New DataSet Dim dt As New DataTable With dt .TableName = "HelpTable" With .Columns .Add("Code", GetType(String)) .Add("HelpText", GetType(String)) End With End With mdataset.Tables.Add(dt) mdataset.DataSetName = "HelpDataset" Else mdataset = New DataSet mdataset.ReadXml("Help.xml") End If listBox1.ValueMember = "Code" listBox1.DataSource = mdataset.Tables(0) End Sub
When the application is closing, the SaveHelpDataset method is called. The code is much simpler because we are using a feature built-in the Dataset object:
Private Sub SaveHelpDataset() mdataset.WriteXml("Help.xml") End Sub
Navigating through the topics
When you want to create a new topic, you can click the New button (the first one from the toolbar). That will just clear the 2 textboxes like this:
Private Sub NewToolStripButton_Click(ByVal sender As Object, ByVal e As EventArgs) Handles NewToolStripButton.Click txtTitle.Clear() txtWiki.Clear() End Sub
You then have to fill the title and the help topic itself and click the Save button from the toolbar (the second button). Notice that the title becomes the key of the topic.
The code for the save topic reads like this:
Private Sub SaveData() If String.IsNullOrWhiteSpace(txtTitle.Text) Then 'Validate that we have a title MessageBox.Show("You need to provide a title for your topic!", "Wiki demo", MessageBoxButtons.OK, MessageBoxIcon.Error) txtTitle.Focus() Return End If Dim dr() As DataRow = mdataset.Tables("HelpTable").Select("Code = '" + txtTitle.Text + "'") If dr.Length > 0 Then 'This topic already exist, update it dr(0).Item("HelpText") = txtWiki.Rtf Else 'This topic is new, create it Dim drNew As DataRow = mdataset.Tables("HelpTable").NewRow drNew.Item("Code") = txtTitle.Text drNew.Item("HelpText") = txtWiki.Rtf mdataset.Tables("HelpTable").Rows.Add(drNew) End If End Sub
When a topic is selected in the Listbox control on the left, the LoadFormData method is called. This method tries to find the selected item in the dataset and fill the textboxes with the values:
Private Sub LoadFormData(ByVal pCurrentForm As String) Dim dr() As DataRow = mdataset.Tables("HelpTable").Select("Code = '" + pCurrentForm + "'") If dr.Length > 0 Then 'This topic already exist txtTitle.Text = dr(0).Item("Code").ToString txtWiki.Rtf = dr(0).Item("HelpText").ToString Else 'This topic is new txtTitle.Text = "New topic" txtWiki.Text = "No help is currently available for this topic" End If End Sub
This code so far should be very easy and know from you. The fun part starts here!
Generating the Word document
As already said, we won’t be using Office automation to generate the Word document. Instead, I rely on Aspose.Words for .Net.
So when you click the “Generate Word doc” button, a lot of things are happening:
The full code of this method reads like this:
Private Sub GenerateBooklet() 'Creates an Aspose.Words document object Dim doc As New Document() 'Creates a DocumentBuilder helper object attached to the current document Dim builder As New DocumentBuilder(doc) 'Using HTML, creates some titles builder.InsertHtml($"<p>Help guide for <b>{AssemblyTitle}</b></p>") builder.InsertHtml($"<p>For the version {AssemblyVersion}</p>") builder.InsertHtml($"<p>{AssemblyCopyright}</p>") builder.InsertHtml($"<p>Document created on {DateTime.Now.ToString("yyyy-MM-dd @ HH:mm", CultureInfo.InvariantCulture)}</p>") builder.InsertHtml($"<p></p>") builder.InsertHtml($"<p></p>") builder.InsertHtml($"<p></p>") builder.InsertHtml($"<p><u>Table of content</u></p>") 'Insert a table of contents at the beginning of the document. builder.InsertTableOfContents("\o ""1-3"" \h \z \u") For Each row As DataRow In mdataset.Tables(0).Rows '// Start on a New page. builder.InsertBreak(BreakType.PageBreak) '// Insert a TC field at the current document builder position. builder.ParagraphFormat.StyleIdentifier = StyleIdentifier.Heading1 builder.Writeln(row.Item("Code").ToString()) Dim rtfdoc As Document = RtfStringToDocument(row.Item("HelpText").ToString()) If rtfdoc IsNot Nothing Then builder.InsertDocument(rtfdoc, ImportFormatMode.KeepSourceFormatting) End If Next 'The newly inserted table of contents will be initially empty. 'It needs to be populated by updating the fields in the document. doc.UpdateFields() 'Save the generated Word document Dim strFileName As String = Path.Combine(Path.GetTempPath(), "UserGuide.docx") doc.Save(strFileName) 'Open the Word document in Word Process.Start(strFileName) End Sub
There are some helper methods and properties that you will find in the downloadable demo.
Need to generate a PDF instead?
Would you like to produce a pdf file instead to prevent user from easily modifying the generated document? It couldn’t more easy. Just change the file extension (from .docx to .pdf) and you are done. This is really a nice feature from the Aspose component.
Conclusion
If I am not mistaken, it is the first time I use a commercial component in one of my article.
Of course, almost everything here could have been done using Office automation but I had my lot of troubles with it in the last years and I have decided a long time ago to never push that again.
Now my users can easily generate a Word (or PDF) document from there wiki-based help system.