Back to Devexpress

Document Management System

wpf-18234-mvvm-framework-services-predefined-set-document-services-document-management-system.md

latest23.4 KB
Original Source

Document Management System

  • Oct 07, 2021
  • 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.

csharp
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; }
}
vb
Public Interface IDocumentManagerService
    <Browsable(False), EditorBrowsable(EditorBrowsableState.Never)> _
    Function CreateDocument(documentType As String, viewModel As Object, parameter As Object, parentViewModel As Object) As IDocument

    Property ActiveDocument() As IDocument
    Event ActiveDocumentChanged As ActiveDocumentChangedEventHandler

    ReadOnly Property Documents() As IEnumerable(Of IDocument)
End Interface

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.

csharp
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);
}
vb
Public Interface IDocument
    Property Id() As Object
    ReadOnly Property Content() As Object
    Property Title() As Object
    Property DestroyOnClose() As Boolean
    Sub Show()
    Sub Hide()
    Sub Close(Optional force As Boolean = True)
End Interface

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.

csharp
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);
       ... 
}
vb
Public NotInheritable Class DocumentManagerServiceExtensions
    ...
    1.  _
    Public Shared Function CreateDocument(service As IDocumentManagerService, viewModel As Object) As IDocument
    End Function
    2.  _
    Public Shared Function CreateDocument(service As IDocumentManagerService, documentType As String, viewModel As Object) As IDocument
    End Function
    3.  _
    Public Shared Function CreateDocument(service As IDocumentManagerService, documentType As String, parameter As Object, parentViewModel As Object) As IDocument
    End Function
    ...
End Class

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.

csharp
var doc = service.CreateDocument(...);
doc.Show();
vb
Dim 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.

csharp
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);
    ...
}
vb
Public NotInheritable Class DocumentManagerServiceExtensions
    ...
    4.  _
    Public Shared Function GetDocumentsByParentViewModel(service As IDocumentManagerService, parentViewModel As Object) As IEnumerable(Of IDocument)
    End Function
    5.  _
    Public Shared Function FindDocument(service As IDocumentManagerService, parameter As Object, parentViewModel As Object) As IDocument
    End Function
    6.  _
    Public Shared Function FindDocument(service As IDocumentManagerService, viewModel As Object) As IDocument
    End Function
    7.  _
    Public Shared Function FindDocumentById(service As IDocumentManagerService, id As Object) As IDocument
    End Function
    ...
End Class

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.

csharp
[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);
    }
}
vb
<POCOViewModel> _
Public Class MainViewModel
    Protected ReadOnly Property DocumentManagerService() As IDocumentManagerService
        Get
            Return Me.GetService(Of IDocumentManagerService)()
        End Get
    End Property

    Public Sub CreateWindowedDocument(arg As Object)
        Dim document As IDocument = DocumentManagerService.CreateDocument()
        document.Id = GetTotalNumberOfDocuments() - 1
        document.Show()
    End Sub

    Private Function GetTotalNumberOfDocuments() As Integer
        Return Enumerable.Count(Of IDocument)(DocumentManagerService.Documents)
    End Function
End Class

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

csharp
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);
    ...
}
vb
Public NotInheritable Class DocumentManagerServiceExtensions
    ...
    8.  _
    Public Shared Function FindDocumentByIdOrCreate(service As IDocumentManagerService, id As Object, createDocumentCallback As Func(Of IDocumentManagerService, IDocument)) As IDocument
    End Function
    9.  _
    Public Shared Sub CreateDocumentIfNotExistsAndShow(service As IDocumentManagerService, ByRef documentStorage As IDocument, documentType As String, parameter As Object, parentViewModel As Object, title As Object)
    End Sub
    ...
End Class

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.

xaml
<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.

csharp
[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);
        };
    }
}
vb
<POCOViewModel> _
Public Class MainViewModel
    Public Overridable Property ActiveDocumentTitle() As String
        Get
            Return m_ActiveDocumentTitle
        End Get
        Set
            m_ActiveDocumentTitle = Value
        End Set
    End Property
    ...
    Private Overridable m_ActiveDocumentTitle As String
    Public Sub ActiveDocumentChanged(e As ActiveDocumentChangedEventArgs)
        If e.NewDocument IsNot Nothing Then
            ActiveDocumentTitle = [String].Format("Title: {0}.", DocumentManagerService.ActiveDocument.Title)
        End If
    End Sub
End Class

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.

csharp
public interface IDocumentContent {
    IDocumentOwner DocumentOwner { get; set; }
    object Title { get; }
    void OnClose(CancelEventArgs e);
    void OnDestroy();
}
vb
Public Interface IDocumentContent
    Property DocumentOwner() As IDocumentOwner
    ReadOnly Property Title() As Object
    Sub OnClose(e As CancelEventArgs)
    Sub OnDestroy()
End Interface

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.

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

csharp
[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
}
vb
<POCOViewModel> _
Public Class WindowedDocumentViewModel
    Implements IDocumentContent
    Public Overridable Property Caption() As String
        Get
            Return m_Caption
        End Get
        Set
            m_Caption = Value
        End Set
    End Property
    Private Overridable m_Caption As String
    Public Overridable Property AllowClose() As Boolean
        Get
            Return m_AllowClose
        End Get
        Set
            m_AllowClose = Value
        End Set
    End Property
    Private Overridable m_AllowClose As Boolean

    Public Sub Close()
        DocumentOwner.Close(Me, False)
    End Sub
    ...
    #Region "IDocumentContent"
    Public Property DocumentOwner() As IDocumentOwner
        Get
            Return m_DocumentOwner
        End Get
        Set
            m_DocumentOwner = Value
        End Set
    End Property
    Private m_DocumentOwner As IDocumentOwner

    Public Sub OnClose(e As System.ComponentModel.CancelEventArgs)
        e.Cancel = Not AllowClose
    End Sub

    Public Sub OnDestroy()
    End Sub

    Public ReadOnly Property Title() As Object
        Get
            Return Caption
        End Get
    End Property
    #End Region
End Class

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.

csharp
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.

csharp
[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.

csharp
[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
}
vb
<POCOViewModel>
Public Class ViewModel
    Inherits ISupportLogicalLayout

    Public ReadOnly Property DocumentManagerService As IDocumentManagerService
        Get
            Return Me.GetService(Of IDocumentManagerService)()
        End Get
    End Property

    Public ReadOnly Property LayoutSerializationService As ILayoutSerializationService
        Get
            Return Me.GetService(Of ILayoutSerializationService)()
        End Get
    End Property

    <Command>
    Public Sub OnWindowClosing()
        Settings.[Default].LogicalLayout = Me.SerializeDocumentManagerService()
        Settings.[Default].RootLayout = LayoutSerializationService.Serialize()
    End Sub

    Public ReadOnly Property CanSerialize As Boolean
        Get
            Return True
        End Get
    End Property

    Public ReadOnly Property LookupViewModels As IEnumerable(Of Object)
        Get
            Return Nothing
        End Get
    End Property
End Class

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