#region Using directives using System; using System.Windows.Forms; using System.Diagnostics; using System.IO; using System.Globalization; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using Microsoft.Win32; using System.Security; #endregion // Using: in the end of this file. namespace DocToolkit { #region Class DocManager /// /// Document manager. Makes file-related operations: /// open, new, save, updating of the form title, /// registering of file type for Windows Shell. /// Built using the article: /// Creating Document-Centric Applications in Windows Forms /// by Chris Sells /// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnforms/html/winforms09182003.asp /// public class DocManager { #region Events public event SaveEventHandler SaveEvent; public event LoadEventHandler LoadEvent; public event OpenFileEventHandler OpenEvent; public event EventHandler ClearEvent; public event EventHandler DocChangedEvent; #endregion #region Members private string fileName = ""; private bool dirty = false; private Form frmOwner; private string newDocName; private string fileDlgFilter; private string registryPath; private bool updateTitle; private const string registryValue = "Path"; private string fileDlgInitDir = ""; // file dialog initial directory #endregion #region Enum /// /// Enumeration used for Save function /// public enum SaveType { Save, SaveAs } #endregion #region Constructor /// /// Initialization /// /// public DocManager(DocManagerData data) { //frmOwner = data.FormOwner; // frmOwner.Closing += OnClosing; updateTitle = data.UpdateTitle; newDocName = data.NewDocName; fileDlgFilter = data.FileDialogFilter; registryPath = data.RegistryPath; if (!registryPath.EndsWith("\\")) registryPath += "\\"; registryPath += "FileDir"; // attempt to read initial directory from registry RegistryKey key = Registry.CurrentUser.OpenSubKey(registryPath); if (key != null) { string s = (string)key.GetValue(registryValue); if (!Empty(s)) fileDlgInitDir = s; } } #endregion #region Public functions and Properties /// /// Dirty property (true when document has unsaved changes). /// public bool Dirty { get { return dirty; } set { dirty = value; SetCaption(); } } /// /// Open new document /// /// public bool NewDocument() { if (!CloseDocument()) return false; SetFileName(""); if (ClearEvent != null) { // raise event to clear document contents in memory // (this class has no idea how to do this) ClearEvent(this, new EventArgs()); } Dirty = false; return true; } /// /// Close document /// /// public bool CloseDocument() { if (!this.dirty) return true; DialogResult res = MessageBox.Show( frmOwner, "Save changes?", Application.ProductName, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation); switch (res) { case DialogResult.Yes: return SaveDocument(SaveType.Save); case DialogResult.No: return true; case DialogResult.Cancel: return false; default: Debug.Assert(false); return false; } } /// /// Open document /// /// /// Document file name. Empty - function shows Open File dialog. /// /// public bool OpenDocument(string newFileName) { // Check if we can close current file if (!CloseDocument()) return false; // Get the file to open if (Empty(newFileName)) { OpenFileDialog openFileDialog1 = new OpenFileDialog(); openFileDialog1.Filter = fileDlgFilter; openFileDialog1.InitialDirectory = fileDlgInitDir; DialogResult res = openFileDialog1.ShowDialog(frmOwner); if (res != DialogResult.OK) return false; newFileName = openFileDialog1.FileName; fileDlgInitDir = new FileInfo(newFileName).DirectoryName; } // Read the data try { using (Stream stream = new FileStream( newFileName, FileMode.Open, FileAccess.Read)) { // Deserialize object from text format IFormatter formatter = new BinaryFormatter(); if (LoadEvent != null) // if caller subscribed to this event { SerializationEventArgs args = new SerializationEventArgs( formatter, stream, newFileName); // raise event to load document from file LoadEvent(this, args); if (args.Error) { // report failure if (OpenEvent != null) { OpenEvent(this, new OpenFileEventArgs(newFileName, false)); } return false; } // raise event to show document in the window if (DocChangedEvent != null) { DocChangedEvent(this, new EventArgs()); } } } } // Catch all exceptions which may be raised from this code. // Caller is responsible to handle all other exceptions // in the functions invoked by LoadEvent and DocChangedEvent. catch (ArgumentNullException ex) { return HandleOpenException(ex, newFileName); } catch (ArgumentOutOfRangeException ex) { return HandleOpenException(ex, newFileName); } catch (ArgumentException ex) { return HandleOpenException(ex, newFileName); } catch (SecurityException ex) { return HandleOpenException(ex, newFileName); } catch (FileNotFoundException ex) { return HandleOpenException(ex, newFileName); } catch (DirectoryNotFoundException ex) { return HandleOpenException(ex, newFileName); } catch (PathTooLongException ex) { return HandleOpenException(ex, newFileName); } catch (IOException ex) { return HandleOpenException(ex, newFileName); } // Clear dirty bit, cache the file name and set the caption Dirty = false; SetFileName(newFileName); if (OpenEvent != null) { // report success OpenEvent(this, new OpenFileEventArgs(newFileName, true)); } // Success return true; } /// /// Save file. /// /// /// public bool SaveDocument(SaveType type) { // Get the file name string newFileName = this.fileName; SaveFileDialog saveFileDialog1 = new SaveFileDialog(); saveFileDialog1.Filter = fileDlgFilter; if ((type == SaveType.SaveAs) || Empty(newFileName)) { if (!Empty(newFileName)) { saveFileDialog1.InitialDirectory = Path.GetDirectoryName(newFileName); saveFileDialog1.FileName = Path.GetFileName(newFileName); } else { saveFileDialog1.InitialDirectory = fileDlgInitDir; saveFileDialog1.FileName = newDocName; } DialogResult res = saveFileDialog1.ShowDialog(frmOwner); if (res != DialogResult.OK) return false; newFileName = saveFileDialog1.FileName; fileDlgInitDir = new FileInfo(newFileName).DirectoryName; } // Write the data try { using (Stream stream = new FileStream( newFileName, FileMode.Create, FileAccess.Write)) { // Serialize object to text format IFormatter formatter = new BinaryFormatter(); if (SaveEvent != null) // if caller subscribed to this event { SerializationEventArgs args = new SerializationEventArgs( formatter, stream, newFileName); // raise event SaveEvent(this, args); if (args.Error) return false; } } } catch (ArgumentNullException ex) { return HandleSaveException(ex, newFileName); } catch (ArgumentOutOfRangeException ex) { return HandleSaveException(ex, newFileName); } catch (ArgumentException ex) { return HandleSaveException(ex, newFileName); } catch (SecurityException ex) { return HandleSaveException(ex, newFileName); } catch (FileNotFoundException ex) { return HandleSaveException(ex, newFileName); } catch (DirectoryNotFoundException ex) { return HandleSaveException(ex, newFileName); } catch (PathTooLongException ex) { return HandleSaveException(ex, newFileName); } catch (IOException ex) { return HandleSaveException(ex, newFileName); } // Clear the dirty bit, cache the new file name // and the caption is set automatically Dirty = false; SetFileName(newFileName); // Success return true; } /// /// Assosciate file type with this program in the Registry /// /// /// true - OK, false - failed public bool RegisterFileType( string fileExtension, string progId, string typeDisplayName) { try { string s = String.Format(CultureInfo.InvariantCulture, ".{0}", fileExtension); // Register custom extension with the shell using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(s)) { // Map custom extension to a ProgID key.SetValue(null, progId); } // create ProgID key with display name using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(progId)) { key.SetValue(null, typeDisplayName); } // register icon using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(progId + @"\DefaultIcon")) { key.SetValue(null, Application.ExecutablePath + ",0"); } // Register open command with the shell string cmdkey = progId + @"\shell\open\command"; using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(cmdkey)) { // Map ProgID to an Open action for the shell key.SetValue(null, Application.ExecutablePath + " \"%1\""); } // Register application for "Open With" dialog string appkey = "Applications\\" + new FileInfo(Application.ExecutablePath).Name + "\\shell"; using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(appkey)) { key.SetValue("FriendlyCache", Application.ProductName); } } catch (ArgumentNullException ex) { return HandleRegistryException(ex); } catch (SecurityException ex) { return HandleRegistryException(ex); } catch (ArgumentException ex) { return HandleRegistryException(ex); } catch (ObjectDisposedException ex) { return HandleRegistryException(ex); } catch (UnauthorizedAccessException ex) { return HandleRegistryException(ex); } return true; } #endregion #region Other Functions /// /// Hanfle exception from RegisterFileType function /// /// /// private bool HandleRegistryException(Exception ex) { Trace.WriteLine("Registry operation failed: " + ex.Message); return false; } ///// ///// Save initial directory to the Registry ///// ///// ///// //private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e) //{ // RegistryKey key = Registry.CurrentUser.CreateSubKey(registryPath); // key.SetValue(registryValue, fileDlgInitDir); //} /// /// Set file name and change owner's caption /// /// private void SetFileName(string fileName) { this.fileName = fileName; SetCaption(); } /// /// Set owner form caption /// private void SetCaption() { if (!updateTitle) return; //frmOwner.Text = string.Format( // CultureInfo.InvariantCulture, // "{0} - {1}{2}", // Application.ProductName, // Empty(this.fileName) ? newDocName : Path.GetFileName(this.fileName), // this.dirty ? "*" : ""); } /// /// Handle exception in OpenDocument function /// /// /// /// private bool HandleOpenException(Exception ex, string fileName) { MessageBox.Show(frmOwner, "Open File operation failed. File name: " + fileName + "\n" + "Reason: " + ex.Message, Application.ProductName); if (OpenEvent != null) { // report failure OpenEvent(this, new OpenFileEventArgs(fileName, false)); } return false; } /// /// Handle exception in SaveDocument function /// /// /// /// private bool HandleSaveException(Exception ex, string fileName) { MessageBox.Show(frmOwner, "Save File operation failed. File name: " + fileName + "\n" + "Reason: " + ex.Message, Application.ProductName); return false; } /// /// Helper function - test if string is empty /// /// /// static bool Empty(string s) { return s == null || s.Length == 0; } #endregion } #endregion #region Delegates public delegate void SaveEventHandler(object sender, SerializationEventArgs e); public delegate void LoadEventHandler(object sender, SerializationEventArgs e); public delegate void OpenFileEventHandler(object sender, OpenFileEventArgs e); #endregion #region Class SerializationEventArgs /// /// Serialization event arguments. /// Used in events raised from DocManager class. /// Class contains information required to load/save file. /// public class SerializationEventArgs : System.EventArgs { private IFormatter formatter; private Stream stream; private string fileName; private bool errorFlag; public SerializationEventArgs(IFormatter formatter, Stream stream, string fileName) { this.formatter = formatter; this.stream = stream; this.fileName = fileName; errorFlag = false; } public bool Error { get { return errorFlag; } set { errorFlag = value; } } public IFormatter Formatter { get { return formatter; } } public Stream SerializationStream { get { return stream; } } public string FileName { get { return fileName; } } } #endregion #region Class OpenFileEventArgs /// /// Open file event arguments. /// Used in events raised from DocManager class. /// Class contains name of file and result of Open operation. /// public class OpenFileEventArgs : System.EventArgs { private string fileName; private bool success; public OpenFileEventArgs(string fileName, bool success) { this.fileName = fileName; this.success = success; } public string FileName { get { return fileName; } } public bool Succeeded { get { return success; } } } #endregion #region class DocManagerData /// /// Class used for DocManager class initialization /// public class DocManagerData { public DocManagerData() { frmOwner = null; updateTitle = true; newDocName = "Untitled"; fileDlgFilter = "All Files (*.*)|*.*"; registryPath = "Software\\Unknown"; } private Form frmOwner; private bool updateTitle; private string newDocName; private string fileDlgFilter; private string registryPath; public Form FormOwner { get { return frmOwner; } set { frmOwner = value; } } public bool UpdateTitle { get { return updateTitle; } set { updateTitle = value; } } public string NewDocName { get { return newDocName; } set { newDocName = value; } } public string FileDialogFilter { get { return fileDlgFilter; } set { fileDlgFilter = value; } } public string RegistryPath { get { return registryPath; } set { registryPath = value; } } }; #endregion } #region Using /* Using: 1. Write class which implements program-specific tasks. This class keeps some data, knows to draw itself in the form window, and implements ISerializable interface. Example: [Serializable] public class MyTask : ISerializable { // class members private int myData; // ... public MyTask() { // ... } public void Draw(Graphics g, Rectangle r) { // ... } // other functions // ... // Serialization // This function is called when file is loaded protected GraphicsList(SerializationInfo info, StreamingContext context) { myData = info.GetInt32("myData"); // ... } // Serialization // This function is called when file is saved public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("myData", myData); // ... } } Add member of this class to the form: private MyClass myClass; 2. Add the DocManager member to the owner form: private DocManager docManager; 3. Add DocManager message handlers to the owner form: private void docManager_ClearEvent(object sender, EventArgs e) { // DocManager executed New command // Clear here myClass or create new empty instance: myClass = new MyClass(); Refresh(); } private void docManager_DocChangedEvent(object sender, EventArgs e) { // DocManager reports that document was changed (loaded from file) Refresh(); } private void docManager_OpenEvent(object sender, OpenFileEventArgs e) { // DocManager reports about successful/unsuccessful Open File operation // For example: if ( e.Succeeded ) // add e.FileName to MRU list else // remove e.FileName from MRU list } private void docManager_LoadEvent(object sender, SerializationEventArgs e) { // DocManager asks to load document from supplied stream try { myClass = (MyClass)e.Formatter.Deserialize(e.SerializationStream); } catch ( catch possible exceptions here ) { // report error e.Error = true; } } private void docManager_SaveEvent(object sender, SerializationEventArgs e) { // DocManager asks to save document to supplied stream try { e.Formatter.Serialize(e.SerializationStream, myClass); } catch ( catch possible exceptions here ) { // report error e.Error = true; } } 4. Initialize docManager member in the form initialization code: DocManagerData data = new DocManagerData(); data.FormOwner = this; data.UpdateTitle = true; data.FileDialogFilter = "MyProgram files (*.mpf)|*.mpf|All Files (*.*)|*.*"; data.NewDocName = "Untitled.mpf"; data.RegistryPath = "Software\\MyCompany\\MyProgram"; docManager = new DocManager(data); docManager.SaveEvent += docManager_SaveEvent; docManager.LoadEvent += docManager_LoadEvent; docManager.OpenEvent += docManager_OpenEvent; docManager.DocChangedEvent += docManager_DocChangedEvent; docManager.ClearEvent += docManager_ClearEvent; docManager.NewDocument(); // Optionally - register file type for Windows Shell bool result = docManager.RegisterFileType("mpf", "mpffile", "MyProgram File"); 5. Call docManager functions when necessary. For example: // File is dropped into the window; // Command line parameter is handled. public void OpenDocument(string file) { docManager.OpenDocument(file); } // User Selected File - Open command. private void CommandOpen() { docManager.OpenDocument(""); } // User selected File - Save command private void CommandSave() { docManager.SaveDocument(DocManager.SaveType.Save); } // User selected File - Save As command private void CommandSaveAs() { docManager.SaveDocument(DocManager.SaveType.SaveAs); } // User selected File - New command private void CommandNew() { docManager.NewDocument(); } 6. Optionally: test for unsaved data in the form Closing event: private void MainForm_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if ( ! docManager.CloseDocument() ) e.Cancel = true; } 7. Optionally: handle command-line parameters in the main function: [STAThread] static void Main(string[] args) { // Check command line if( args.Length > 1 ) { MessageBox.Show("Incorrect number of arguments. Usage: MyProgram.exe [file]", "MyProgram"); return; } // Load main form, taking command line into account MainForm form = new MainForm(); if ( args.Length == 1 ) form.OpenDocument(args[0]); // OpenDocument calls docManager.OpenDocument Application.Run(form); } */ #endregion