Document Management Toolkit Library

Introduction

DocToolkit library is a set of classes which may be used to create full-featured Windows Forms applications for document management. Suppose we want to create an application with the following functions:

  • Open file, editing file in the window, Save, Save As functions, testing for dirty state (document is changed), displaying of current file name in the form title.
  • Associating of file type with the program for Windows Shell.
  • Handling files passed to the program in the command line and dropped from the Windows Explorer.
  • Managing list of most recently used files (MRU).
  • Persisting of the last application window state.

Reading this list of functions, we can see that editing file in the window is program-specific and changes in every project. All other tasks may be encapsulated to number of classes and used by a unified way. DocToolkit library is a framework which may be used to develop such applications.

DocToolkit classes

DocToolkit library contains a number of classes. Some of them are published in the CodeProject in my previous articles, two classes use code from another articles. Links to the class source is provided in class description in the article and in the class code. All classes have a unified programming interface; source code is tested for conformance to the Microsoft .NET Framework Design Guidelines using the FxCop tool.

  • DocManager class. Makes file-related operations: Open, New, Save, updating of the form title, registering of file type for Windows Shell. This class is written using the MSDN Magazine article “Creating Document-Centric Applications in Windows Forms” by Chris Sells

    This class has a number of functions which are called by owner form, like NewDocumentOpenDocumentSaveDocument. The class raises a number of events which should be handled in the owner form, like SaveEventLoadEvent and DocChangedEvent. For handling these events, the owner form provides task-specific code for serialization, refreshing information in the window etc.

  • DragDropManager class. Encapsulates the code required to open files dropped to the program window from Windows Explorer. Class subscribes to the owner form DragEnter and DragDrop events and raises event FileDroppedEvent passing file name(s) to open.
  • MruManager class. Manages list of recently opened files (MRU) – shows it in the owner form menu and saves it to the Registry. When user selects file from the MRU list, class raises MruOpenEvent event which is handled in the owner form.
  • PersistWindowState class. Keeps main application window state when program exits, and restores this state on the next run. Class is written by Joel Matthias and published in the article: Saving and Restoring the Location, Size and Window State of a .NET Form.

Using the DocToolkit Library

Step-by-step instructions to create Windows Forms application using the DocToolkit library.

  1. Create new C# Windows Forms application. Demo application is called UIDocSample.
  2. Add reference to the DocToolkit Library. Ensure that DocToolkit.dll is available at runtime. Demo projects DocToolkit and UIDocSample have output directories ..\bin\Debug\ and ..\bin\Release\, so both DLL and exe files are written to the same directory.
  3. Add MainMenu control to the form. Add File popup menu and add the following menu items to it:
    Item Text Item Name
    New menuFileNew
    Open menuFileOpen
    Save menuFileSave
    Save As menuFileSaveAs
    – (separator)
    Recent Files menuFileRecent
    – (separator)
    Exit menuFileExit
  4. Add new class to the project. Call it DocClass. Paste the following code to this class file:
    Collapse
    using System;
    using System.Runtime.Serialization;
    using System.Windows.Forms;
    using System.Drawing;
    using System.Security.Permissions;
    using System.Globalization;
    
    namespace UIDocSample
    {
        /// <summary>
    
        /// Document class which makes program-specific tasks
    
        /// </summary>
    
        [Serializable]
        public class DocClass  : ISerializable
        {
            private int sampleData;
    
            public DocClass()
            {
                sampleData = 0;
            }
    
            public bool Empty
            {
                get
                {
                    return (sampleData == 0);
                }
            }
    
            public void Draw(Graphics g)
            {
                Font drawFont = new Font("Arial", 16);
                SolidBrush drawBrush = new SolidBrush(Color.Black);
                PointF drawPoint = new PointF(10.0F, 10.0F);
                g.DrawString(sampleData.ToString(CultureInfo.InvariantCulture), 
                                               drawFont, drawBrush, drawPoint);
            }
    
            public void Change()
            {
                sampleData++;
            }
    
            // Serialization
    
            // This function is called when file is loaded
    
            protected DocClass(SerializationInfo info, 
                                 StreamingContext context)
            {
                sampleData = info.GetInt32("sampleData");
            }
    
            // Serialization
    
            // This function is called when file is saved
    
            [SecurityPermissionAttribute(SecurityAction.Demand, 
                                   SerializationFormatter=true)]
            public virtual void GetObjectData(SerializationInfo info, 
                                               StreamingContext context)
            {
                info.AddValue("sampleData", sampleData);
            }
    
        }
    }

    This class is prototype for a class which performs program-specific tasks. It has some data, has function for changing of this data, knows to draw itself to the Graphics object and supports ISerializable interface, this allows to save/load class instance.

  5. Add DocClass member to the Form1 class and initialize it in the class constructor:
    Collapse
            private DocClass docClass;
    
            public Form1()
            {
                InitializeComponent();
    
                docClass = new DocClass();
            }

    Add Paint and MouseDown event handlers to the Form1 class and fill them by the following way:

    Collapse
            private void Form1_Paint(object sender, 
                       System.Windows.Forms.PaintEventArgs e)
            {
                 docClass.Draw(e.Graphics);     
            }
    
            private void Form1_MouseDown(object sender, 
                       System.Windows.Forms.MouseEventArgs e)
            {
                if ( e.Button == MouseButtons.Left )
                {
                    docClass.Change();
                    Refresh();
                }
            }

    Now, we have the prototype of the application which manages some data in the window and changes it by clicking on the window client area. The next steps show how to add DocToolkit helper classes to this application.

  6. Add DocManager member to the Form1 class:
    Collapse
            private DocManager docManager;

    Add the following functions to the Form1 class:

    Collapse
            private void docManager_ClearEvent(object sender, EventArgs e)
            {
                // DocManager reports that document should be cleared
    
                // (user selected New command)
    
                docClass = new DocClass();
                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
    
                // Will be filled later
    
            }
    
            private void docManager_LoadEvent(object sender, 
                                          SerializationEventArgs e)
            {
                // DocManager asks to load document from supplied stream
    
                try
                {
                    docClass  = 
                      (DocClass)e.Formatter.Deserialize(e.SerializationStream);
                }
                catch ( Exception ex )
                {
                    MessageBox.Show(this, 
                        "Open File operation failed. File name: " 
                        + e.FileName + "\n" +
                        "Reason: " + ex.Message, 
                        Application.ProductName);
    
                    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, docClass);
                }
                catch ( Exception ex )
                {
                    MessageBox.Show(this, 
                        "Save File operation failed. File name: " 
                        + e.FileName + "\n" +
                        "Reason: " + ex.Message, 
                        Application.ProductName);
    
                    e.Error = true;
                }
    
            }
  7. Add function InitializeHelperObjects to the Form1 class:
    Collapse
            void InitializeHelperObjects()
            {
                string registryPath = "Software\\YourCompany\\UIDocSample";
    
                // docManager
    
                DocManagerData data = new DocManagerData();
                data.FormOwner = this;
                data.UpdateTitle = true;
                data.FileDialogFilter = "UIDocSample files" + 
                         " (*.uds)|*.uds|All Files (*.*)|*.*";
                data.NewDocName = "Untitled.uds";
                data.RegistryPath = registryPath;
    
                docManager = new DocManager(data);
    
                // Subscribe to DocManager events
    
                docManager.SaveEvent += 
                          new SaveEventHandler(docManager_SaveEvent);
                docManager.LoadEvent += 
                          new LoadEventHandler(docManager_LoadEvent);
                docManager.OpenEvent += 
                          new OpenFileEventHandler(docManager_OpenEvent);
                docManager.DocChangedEvent += 
                          new EventHandler(docManager_DocChangedEvent);
                docManager.ClearEvent += 
                          new EventHandler(docManager_ClearEvent);
    
                docManager.NewDocument();
    
                // Register file type for Windows Shell
    
                docManager.RegisterFileType("uds", "udsfile", 
                                            "UIDocSample File");            
            }

    Call InitializeHelperObjects function from Form1 constructor:

    Collapse
            public Form1()
            {
                InitializeComponent();
    
                docClass = new DocClass();
                InitializeHelperObjects();
            }

    Open function Form1_MouseDown added before and add these lines to it:

    Collapse
            private void Form1_MouseDown(object sender, 
                            System.Windows.Forms.MouseEventArgs e)
            {
                if ( e.Button == MouseButtons.Left )
                {
                    docClass.Change();
                    docManager.Dirty = true;        // add this line
    
                    Refresh();
                }
            }
  8. Add functions to the Form1 class which call various DocManager functions:
    Collapse
            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();
            }
  9. Add handlers to New, Open, Save, Save As menu items and Closing event:
    Collapse
            private void menuFileNew_Click(object sender, System.EventArgs e)
            {
                CommandNew();        
            }
    
            private void menuFileOpen_Click(object sender, System.EventArgs e)
            {
                CommandOpen();
            }
    
            private void menuFileSave_Click(object sender, System.EventArgs e)
            {
                CommandSave();
            }
    
            private void menuFileSaveAs_Click(object sender, System.EventArgs e)
            {
                CommandSaveAs();
            }
    
            private void Form1_Closing(object sender, 
                         System.ComponentModel.CancelEventArgs e)
            {
                if ( ! docManager.CloseDocument() )
                    e.Cancel = true;
            }
  10. Change Main function by the following way:
    Collapse
            [STAThread]
            static void Main(string[] args) 
            {
                // Check command line
    
                if( args.Length > 1 ) 
                {
                    MessageBox.Show("Incorrect number" + 
                      " of arguments. Usage: UIDocSample.exe [file]", 
                      "UIDocSample");
                    return;
                }
    
                Form1 form = new Form1();
    
                if ( args.Length == 1 ) 
                    // OpenDocument calls docManager.OpenDocument
    
                    form.OpenDocument(args[0]);
    
                Application.Run(form);
            }

    DocManager is the most complicated class in the DocToolkit library. Adding of other classes is relatively simple.

  11. DragDropManager class support.Add function which is used to open file dropped to the from:
    Collapse
            private void dragDropManager_FileDroppedEvent(object sender, 
                                                   FileDroppedEventArgs e)
            {
                OpenDocument(e.FileArray.GetValue(0).ToString());
            }

    Add DragDropManager member to the Form1 class:

    Collapse
            private DragDropManager dragDropManager;

    and initialize it in the InitializeHelperObjects function:

    Collapse
            void InitializeHelperObjects()
            {
              // ...
    
              // dragDropManager
    
              dragDropManager = new DragDropManager(this);
              dragDropManager.FileDroppedEvent += new 
               FileDroppedEventHandler(this.dragDropManager_FileDroppedEvent); 
            }
  12. MruManager class support.Add function which is used to open file selected from the MRU list:
    Collapse
            private void mruManager_MruOpenEvent(object sender, 
                                          MruFileOpenEventArgs e)
            {
                OpenDocument(e.FileName);
            }

    Add MruManager member to the Form1 class:

    Collapse
            private MruManager mruManager;

    and initialize it in the InitializeHelperObjects function:

    Collapse
            void InitializeHelperObjects()
            {
                // ...
    
                // mruManager
    
                mruManager = new MruManager();
                mruManager.Initialize(
                    this,               // owner form
    
                    menuFileRecent,     // Recent Files menu item
    
                    registryPath);      // Registry path to keep MRU list
    
                mruManager.MruOpenEvent += new 
                  MruFileOpenEventHandler(this.mruManager_MruOpenEvent);
            }

    Open function docManager_OpenEvent added before, and add this code to it:

    Collapse
            private void docManager_OpenEvent(object sender, 
                                                  OpenFileEventArgs e)
            {
                // DocManager reports about
    
                // successful/unsuccessful Open File operation
    
                if ( e.Succeeded )
                {
                    mruManager.Add(e.FileName);
                }
                else
                {
                    mruManager.Remove(e.FileName);
                }
            }
  13. PersistWindowState class support.Add PersistWindowState member to the Form1 class:
    Collapse
            private PersistWindowState persistState;

    and initialize it in the InitializeHelperObjects function:

    Collapse
            void InitializeHelperObjects()
            {
                // ...
    
                // persistState
    
                persistState = new PersistWindowState(registryPath, this);
            }
  14. Build the program and run it. In its initial state, it shows 0 in the window. Clicking in the window client area increments this number. Current file name is shown in the form title. When document has unsaved information, file name is followed by asterisk. Program has New, Open, Save, and Save As functions. If information is currently unsaved, and user tries to close the form or execute New or Open commands, program asks confirmation. Program may open file from the command line and file dropped from Windows Explorer. uds file type is associated with this program, so you can double-click on such a file in Windows Explorer and program opens it.Program has MRU files list. Every successfully opened file is added to this list. Program window is opened in the state it was closed last time. Open Registry key HKEY_CURRENT_USER\Software\YourCompany\UIDocSample. You can see information written by DocToolkit helper classes under this key. To convert this sample to real application, replace DocClass class with class which implements your application-specific tasks.

Setting controls state at application idle time

Commands in the UIDocSample program may be executed by selecting the menu items. Generally, applications may have also toolbar, buttons and other controls. In different situations, these controls may have different states: enabled/disabled, checked, visible/invisible etc. Every user action may change the controls’ state. We need generic way to do this in the program. The way I am using is setting controls’ state at application idle time. This way is very good for toolbar buttons and dialog controls. The best way for updating of menu items is handling of the Popup event. However, they may be handled at idle time as well.

Suppose we want to keep Save As command enabled when DocClass is not in its initial state (not empty). Save command should be enabled ifDocClass is not empty and last changes are not saved yet. Add the following line to the Form1 constructor:

Collapse
            Application.Idle += new EventHandler(Application_Idle);

and add the Application_Idle function:

Collapse
        private void Application_Idle(object sender, EventArgs e)
        {
            menuFileSaveAs.Enabled = (!docClass.Empty);
            menuFileSave.Enabled = (docManager.Dirty & (!docClass.Empty));

            // State of any controls may be set here:

            // menu items, toolbar buttons, dialog controls etc.

        }

Now, a menu item’s state is changed together with program state. This way works like ON_UPDATE_COMMAND_UI MFC handler for toolbar buttons.

Conclusion

When writing Windows Forms applications for document management, we need to implement the same standard tasks in every project. .NET is missing MFC-style framework for creating applications of this type. DocToolkit library is attempting to create such a framework. Developingapplications using this library allows to concentrate on implementation of project-specific tasks, having standard document management stuff in the DocToolkit classes.

Client program shown in this article is very simple. The question is whether DocToolkit library is good enough to be used in real world applications. In my next article, I want to introduce the DrawTools program which shows how to draw graphic objects on the window client area using mouse. This program uses DocToolkit library and I found it convenient to use this library in it.

Acknowledgements

  1. Chris SellsCreating Document-Centric Applications in Windows Forms.
  2. Joel MatthiasSaving and Restoring the Location, Size and Window State of a .NET Form.

 

About eagle081183

Passionate, Loyal
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s