Load and Save Documents
Users expect an editor to open their files, save their work and warn them before unsaved changes are lost. The WPF TextControl shows its own Open and Save As dialog boxes and also reads and writes files and byte arrays without any dialog. Using the window of your first WPF application, you add a File menu with New, Open, Save, Save As and Exit, keep track of the current file, and ask before a changed document is discarded. The last section moves the same commands into the application menu of a ribbon.
Prerequisites
- The window from Create Your First WPF Application, with the
TextControlnamedtextControl1and its bars in aDockPanel. - The Professional or Enterprise edition for the formats DOCX and PDF that the code offers. The Standard edition reads and writes RTF and HTML; see Editions and Feature Availability.
How Loading and Saving Works
Load replaces the complete contents of the editor with a document, and Save writes the complete document. Both have two kinds of overloads:
- With a dialog box. Without a file name,
Loadopens the Open dialog box andSavethe Save As dialog box. The StreamType values you pass decide which file format filters the dialog offers. These overloads return a TXTextControl.WPF.DialogResult:OK,Cancel, orNonewhen the user left the dialog with the ESC key. - Without a dialog box. With a file path, a stream, a byte array or a string, the methods load and save directly.
Savecreates the file or overwrites it.
Supported File Formats lists the formats and their StreamType values.
Write the Commands
Replace the contents of MainWindow.xaml.cs with the following code:
using System;
using System.ComponentModel;
using System.IO;
using System.Windows;
using TXTextControl;
namespace MyWPFApp
{
public partial class MainWindow : Window
{
// The formats offered in the Open and Save As dialog boxes
private const StreamType DocumentFormats =
StreamType.WordprocessingML | StreamType.RichTextFormat |
StreamType.HTMLFormat | StreamType.AdobePDF;
private string currentFile; // null until the document has a file
private StreamType currentStreamType;
private bool isModified;
public MainWindow()
{
InitializeComponent();
UpdateTitle();
}
private void textControl1_Loaded(object sender, RoutedEventArgs e)
{
textControl1.Focus();
}
private void New_Click(object sender, RoutedEventArgs e)
{
if (!ConfirmSaveChanges())
{
return;
}
textControl1.ResetContents();
currentFile = null;
isModified = false;
UpdateTitle();
}
private void Open_Click(object sender, RoutedEventArgs e)
{
if (!ConfirmSaveChanges())
{
return;
}
LoadSettings loadSettings = new LoadSettings()
{
// Import the merge fields of Word documents as fields
ApplicationFieldFormat = ApplicationFieldFormat.MSWord
};
// Show the Open dialog box. Cancel leaves the document unchanged.
if (textControl1.Load(DocumentFormats | StreamType.AllFormats, loadSettings)
!= TXTextControl.WPF.DialogResult.OK)
{
return;
}
currentFile = loadSettings.LoadedFile;
currentStreamType = loadSettings.LoadedStreamType;
isModified = false;
UpdateTitle();
}
private void Save_Click(object sender, RoutedEventArgs e)
{
SaveDocument();
}
private void SaveAs_Click(object sender, RoutedEventArgs e)
{
SaveDocumentAs();
}
private void Exit_Click(object sender, RoutedEventArgs e)
{
// Closing asks about unsaved changes
Close();
}
private bool SaveDocument()
{
// A new document has no file yet
if (currentFile == null)
{
return SaveDocumentAs();
}
textControl1.Save(currentFile, currentStreamType);
isModified = false;
UpdateTitle();
return true;
}
private bool SaveDocumentAs()
{
SaveSettings saveSettings = new SaveSettings();
// Show the Save As dialog box
if (textControl1.Save(DocumentFormats, saveSettings)
!= TXTextControl.WPF.DialogResult.OK)
{
return false;
}
currentFile = saveSettings.SavedFile;
currentStreamType = saveSettings.SavedStreamType;
isModified = false;
UpdateTitle();
return true;
}
private void textControl1_Changed(object sender, EventArgs e)
{
if (!isModified)
{
isModified = true;
UpdateTitle();
}
}
private void Window_Closing(object sender, CancelEventArgs e)
{
if (!ConfirmSaveChanges())
{
e.Cancel = true;
}
}
// Returns false if the user canceled
private bool ConfirmSaveChanges()
{
if (!isModified)
{
return true;
}
MessageBoxResult result = MessageBox.Show(this,
"Do you want to save the changes to the document?",
"Save Changes",
MessageBoxButton.YesNoCancel,
MessageBoxImage.Question);
if (result == MessageBoxResult.Cancel)
{
return false;
}
if (result == MessageBoxResult.Yes)
{
return SaveDocument();
}
return true;
}
private void UpdateTitle()
{
string name = currentFile == null ? "Untitled" : Path.GetFileName(currentFile);
Title = name + (isModified ? "*" : "") + " - My Editor";
}
}
}
The code works as follows:
- New. ResetContents deletes the complete contents of the editor.
- Open.
LoadwithStreamTypevalues and a LoadSettings object shows the Open dialog box with the filters of these formats.StreamType.AllFormatsadds an item that lists every file the dialog can open. After the user has opened a file, LoadedFile and LoadedStreamType contain its path and format. - Word fields. ApplicationFieldFormat set to
MSWordimports the fields defined by Microsoft Word, such as merge fields, into ApplicationFields. - The dialog result. The type is written in full,
TXTextControl.WPF.DialogResult, because inside a WPF window the nameDialogResultrefers to the window's ownDialogResultproperty. - Save. A document that has a file is saved to it in its format. A new document goes to Save As.
- Save As.
SavewithStreamTypevalues and a SaveSettings object shows the Save As dialog box. SavedFile and SavedStreamType contain the path and format the user chose. - Unsaved changes. The
Changedhandler marks the document as modified and shows an asterisk in the title.ConfirmSaveChangesasks before New, Open or closing the window discards the changes; settinge.CancelinWindow_Closingkeeps the window open.
Press F5, type some text and choose File > Save As.... Save the document as DOCX, change it, and choose File > New: the application asks whether to save the changes.
Load and Save Without a Dialog Box
For files your code knows, pass the path and the format:
textControl1.Load("template.docx", StreamType.WordprocessingML);
textControl1.Save("letter.rtf", StreamType.RichTextFormat);
A document from a database or a web service usually arrives as a byte array. Load and Save take it with a BinaryStreamType:
// Load a document from a byte array
byte[] data = File.ReadAllBytes("template.docx");
textControl1.Load(data, BinaryStreamType.WordprocessingML);
// Save the document to a byte array
byte[] docx;
textControl1.Save(out docx, BinaryStreamType.WordprocessingML);
Save creates the array; you declare the variable only. With BinaryStreamType.AdobePDF, the same call returns a PDF document.
Call these methods from an event handler or from the Loaded handler of the TextControl, not from the constructor of the window: the editor is ready for a document only once it has been loaded into the window.
Save Document Properties
A SaveSettings object passed to Save also carries the properties of the document, such as its author and title, which are saved in DOCX and PDF files. Create a PDF sets them step by step. When a document is loaded, DocumentSettings of the TextControl contains its properties.
Add the Commands to the Ribbon
In the ribbon window of Create a WPF Ribbon Application, the commands belong in the application menu, which opens from the button at the left of the tabs. The WPF ribbon defines it with Ribbon.ApplicationMenu; add it as the first element inside the Ribbon:
<Ribbon x:Name="ribbon1" DockPanel.Dock="Top">
<Ribbon.ApplicationMenu>
<RibbonApplicationMenu>
<RibbonApplicationMenuItem Header="New" Click="New_Click" />
<RibbonApplicationMenuItem Header="Open..." Click="Open_Click" />
<RibbonApplicationMenuItem Header="Save" Click="Save_Click" />
<RibbonApplicationMenuItem Header="Save As..." Click="SaveAs_Click" />
<RibbonApplicationMenuItem Header="Exit" Click="Exit_Click" />
</RibbonApplicationMenu>
</Ribbon.ApplicationMenu>
<WPF:RibbonFormattingTab x:Name="ribbonFormattingTab1" />
<!-- the other tabs -->
</Ribbon>
The Click handlers are the methods above, unchanged, in the class derived from RibbonWindow. The TX Words sample gives the menu items the icons and texts of TX Text Control with the ResourceProvider class, and adds recent files and a Quick Access Toolbar. Its source code is in %USERPROFILE%\Documents\TX Text Control 34.0.NET for WPF\CSharp\TX Words\Ribbon.
Next Steps
- Create a Document Programmatically: build a document in code.
- Create a PDF: PDF options such as passwords and PDF/A.
- Loading and Saving Documents: all load and save options of the editor in detail.
- Supported File Formats: the formats you can load and save.