wpf-18234-mvvm-framework-services-predefined-set-document-services-document-management-system.md
This topic describes the common concepts behind services that implement the IDocumentManagerService interface.
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; }
}
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.
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);
}
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.
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);
...
}
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.
var doc = service.CreateDocument(...);
doc.Show();
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.
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);
...
}
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.
[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);
}
}
<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.
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);
...
}
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.
<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);
};
}
}
<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
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();
}
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.
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
}
<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
IDocumentManagerService implementations allow you to save layout information about opened documents. Layout information can be divided into two parts.
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:
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
}
<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.