Skip to main content

Document Management System

  • 11 minutes to read

This topic describes the common concepts behind services that implement the IDocumentManagerService interface.

Document Manager Service and Documents Concepts

The IDocumentManagerService is an abstraction of the document manager that works with document-objects representing Views and ViewModels. The DevExpress MVVM Framework includes several implementations of the IDocumentManagerService interface that are associated with different DevExpress WPF Controls and as a result uses different forms to display their documents. Nevertheless, each of these services uses the common document management mechanism provided by IDocumentManagerService to control documents.

The IDocumentManagerService interface definition is represented below.

public interface IDocumentManagerService {
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    IDocument CreateDocument(string documentType, object viewModel, object parameter, object parentViewModel);

    IDocument ActiveDocument { get; set; }
    event ActiveDocumentChangedEventHandler ActiveDocumentChanged;

    IEnumerable<IDocument> Documents { get; }
}

All Views and ViewModels injected in a Document Manager are stored as collections of document-objects in the IDocumentManagerService.Documents property. Each document in a collection is an object implementing the IDocument interface (see its code below). Note that documents for different services may have a different structure regardless of owner-service specifics.

public interface IDocument {
    object Id { get; set; }
    object Content { get; }
    object Title { get; set; }
    bool DestroyOnClose { get; set; }
    void Show();
    void Hide();
    void Close(bool force = true);
}

The IDocument interface provides the base methods and properties to work with the document.

Note

The IDocument.Id property value is used as a control name and should conform to the XamlName Grammar.

The CreateDocument method cannot be browsed, because there is no need to use all four parameters simultaneously. Instead, there are several extension methods from the DocumentManagerServiceExtensions class.

public static class DocumentManagerServiceExtensions {
       ... 
    1. public static IDocument CreateDocument(this IDocumentManagerService service, object viewModel);
    2. public static IDocument CreateDocument(this IDocumentManagerService service, string documentType, object viewModel);
    3. public static IDocument CreateDocument(this IDocumentManagerService service, string documentType, object parameter, object parentViewModel);
       ... 
}

Method 1 is used when a document View is defined through the ViewServiceBase.ViewTemplate or ViewServiceBase.ViewTemplateSelector property and at the same time, the document View should not contain a View Model, because it is passed through the service.

Method 2 and Method 3 create a document View by implicitly calling the ViewLocator and pass the specified ViewModel to the created View. To learn how the view creation works using the ViewLocator, see the following topic: View creation mechanisms.

When the document is created, use the IDocument.Show method to display it.

var doc = service.CreateDocument(...);
doc.Show();

This method works differently, based on which IDocumentManagerService implementation you use. For instance, if you use the WindowedUIDocumentManagerService, the document will be shown in a separate window.

When a document is created, it can be obtained with the IDocumentManagerService.Documents property. Alternatively, there are several built-in methods in the DocumentManagerServiceExtensions class that perform searching.

public static class DocumentManagerServiceExtensions {
    ...
    4. public static IEnumerable<IDocument> GetDocumentsByParentViewModel(this IDocumentManagerService service, object parentViewModel);
    5. public static IDocument FindDocument(this IDocumentManagerService service, object parameter, object parentViewModel);
    6. public static IDocument FindDocument(this IDocumentManagerService service, object viewModel);
    7. public static IDocument FindDocumentById(this IDocumentManagerService service, object id);
    ...
}

The DocumentManagerServiceExtensions.GetDocumentsByParentViewModel method (Method 4) returns a collection of documents whose ParentViewModel is equal to the ViewModel passed as a parameter. To use this method, the document View Model should implement the ISupportParentViewModel interface. Otherwise, this method returns an empty collection. Method 5 requires you to implement the ISupportParameter interface by the document’s ViewModel and returns the first document with a specified parameter and parent ViewModel. Method 6 and Method 7 retrieve and return a document with a specified ViewModel or id accordingly.

Note

Searching by the ID requires the IDocument.Id property to be initialized with a unique value.

[POCOViewModel]
public class MainViewModel {
    protected IDocumentManagerService DocumentManagerService { get { return this.GetService<IDocumentManagerService>(); } }

    public void CreateWindowedDocument(object arg) {
        IDocument document = DocumentManagerService.CreateDocument( ... );
        document.Id = GetTotalNumberOfDocuments() - 1;
        document.Show();            
    }

    int GetTotalNumberOfDocuments() {
        return Enumerable.Count<IDocument>(DocumentManagerService.Documents);
    }
}

In addition to the base methods, the DocumentManagerServiceExtensions class provides two methods.

public static class DocumentManagerServiceExtensions {
    ...
    8. public static IDocument FindDocumentByIdOrCreate(this IDocumentManagerService service, object id, Func<IDocumentManagerService, IDocument> createDocumentCallback);
    9. public static void CreateDocumentIfNotExistsAndShow(this IDocumentManagerService service, ref IDocument documentStorage, string documentType, object parameter, object parentViewModel, object title);
    ...
}

The DocumentManagerServiceExtensions.FindDocumentByIdOrCreate method retrieves and returns a document with a specific Id. If such a document does not exist, it will be created and returned by the method.

The DocumentManagerServiceExtensions.CreateDocumentIfNotExistsAndShow method creates and shows a new document if a document with the specified parameters does not exist.

The active document can be obtained using the IDocumentManagerService.ActiveDocument property. After the active document has been changed, the IDocumentManagerService.ActiveDocumentChanged event is raised. To subscribe this event, you can use the EventToCommand class or create an event handler for the ActiveDocumentChanged event in your ViewModel.

<dxmvvm:Interaction.Behaviors>
    <dx:WindowedDocumentUIService>
        <dxmvvm:Interaction.Behaviors>
            <dxmvvm:EventToCommand EventName="ActiveDocumentChanged" Command="{Binding ActiveDocumentChangedCommand}" PassEventArgsToCommand="True"/>
        </dxmvvm:Interaction.Behaviors>
    </dx:WindowedDocumentUIService>
</dxmvvm:Interaction.Behaviors>

In the code snippet above, the ActiveDocumentChanged event is bound to the ActiveDocumentChangedCommand command. The implementation of this command is shown in the code snippet below.

[POCOViewModel]
public class MainViewModel {
    public virtual string ActiveDocumentTitle { get; set; }
    ...
    public void ActiveDocumentChanged(ActiveDocumentChangedEventArgs e) {
        if (e.NewDocument != null) {
            ActiveDocumentTitle = String.Format("Title: {0}.", DocumentManagerService.ActiveDocument.Title);
        };
    }
}

Implementing Document and ViewModel Interaction

To organize relationships between a ViewModel and its document created by IDocumentManagerService, implement the IDocumentContent interface at the ViewModel level.

public interface IDocumentContent {
    IDocumentOwner DocumentOwner { get; set; }
    object Title { get; }
    void OnClose(CancelEventArgs e);
    void OnDestroy();
}

This interface provides the following capabilities.

  • Use the IDocumentContent.Title property to specify the document’s title. The IDocument.Title has higher priority than IDocumentContent.Title.
  • Control the document’s closing and erasing with the IDocumentContent.OnClose and IDocumentContent.OnDestroy methods. OnClose is an event handler that is raised when the document is going to be closed. By using the OnClose method’s arguments, you can prevent the document from being closed. The OnDestroy method is called after the document is closed.
  • Access the document’s service-owner by using the IDocumentContent.DocumentOwner property of the IDocumentOwner type. The IDocumentOwner interface provides a single method: IDocumentOwner.Close, which allows you to close a document specified in the documentContent parameter.

    public interface IDocumentOwner {
        void Close(IDocumentContent documentContent, bool force = true);
    }
    

Below is an example that illustrates how to use the IDocumentContent and IDocumentOwner interfaces.

[POCOViewModel]
public class WindowedDocumentViewModel : IDocumentContent {
    public virtual string Caption { get; set; }
    public virtual bool AllowClose { get; set; }
    ...
    public void Close() {
        DocumentOwner.Close(this, false);
    }
    ...
    #region IDocumentContent
    public IDocumentOwner DocumentOwner { get; set; }

    public void OnClose(System.ComponentModel.CancelEventArgs e) {
        e.Cancel = !AllowClose;
    }

    public void OnDestroy() { }

    public object Title {
        get { return Caption; }
    } 
    #endregion
}

Document Manager Serialization

IDocumentManagerService implementations allow you to save layout information about opened documents. Layout information can be divided into two parts.

  • Logical Layout
  • Visual Layout

The Logical Layout contains document descriptions (title, id, type, visibility, etc). Additionally, you can save information about the document’s ViewModel state by implementing the ISupportLogicalLayout generic interface at the ViewModel level.

The ISupportLogicalLayout interface is shown below.

public interface ISupportLogicalLayout {
    bool CanSerialize { get; }
    IDocumentManagerService DocumentManagerService { get; }
    IEnumerable<object> LookupViewModels { get; }
}

public interface ISupportLogicalLayout<T> : ISupportLogicalLayout {
    T SaveState();
    void RestoreState(T state);
}

This interface provides the following capabilites:

  • CanSerialize - specifies whether ViewModel’s state can be serialized.
  • DocumentManagerService - specifies the service owner.
  • SaveState/RestoreState methods to save/restore ViewModel’s state.
  • LookupViewModels specifies a collection of ViewModels of child documents.

To serialize\deserialize the logical part, use the SerializeDocumentManagerService extension method that returns the saved layout as a string object.

[POCOViewModel]
public class ViewModel : ISupportLogicalLayout {
    ...
    public void OnWindowClosing() {
        Settings.Default.LogicalLayout = this.SerializeDocumentManagerService();
        ....
    }
    ...

    #region ISupportLogicalLayout
    public bool CanSerialize {
        get { return true; }
    }

    public IEnumerable<object> LookupViewModels {
        get { return null; }
    }
    #endregion
}

The Visual Layout - includes information about controls displayed within a document. To serialize\deserialize the visual layout, use the LayoutSerializationService.

[POCOViewModel]
public class ViewModel : ISupportLogicalLayout {

    public IDocumentManagerService DocumentManagerService { get { return this.GetService<IDocumentManagerService>(); } }
    public ILayoutSerializationService LayoutSerializationService { get { return this.GetService<ILayoutSerializationService>(); } }

    [Command]
    public void OnWindowClosing() {
        Settings.Default.LogicalLayout = this.SerializeDocumentManagerService();
        Settings.Default.RootLayout = LayoutSerializationService.Serialize();
        ...
    }

    #region ISupportLogicalLayout
    public bool CanSerialize {
        get { return true; }
    }

    public IEnumerable<object> LookupViewModels {
        get { return null; }
    }
    #endregion
}

To learn how to use this service, see the LayoutSerializationService topic. A sample illustrating this feature in action is available here.

Important

To properly save and restore documents, follow these recommendations.

  • The Document.Id property should be initialized with an unique value that is defined in accordance with XamlName Grammar.
  • A document ViewModel should be defined in XAML at the document’s View level.
  • A document ViewModel should implement the ISupportLogicalLayout interface. Otherwise, the ViewModel’s state will not be serialized.
  • An object representing a ViewModel’s state should support serialization/deserialization by using the Data Contracts serialization engine