Question

DockSite layout serialization and DocumentItemsSource

Posted 3 months ago by Avatar Barry smith

I have an app which features a docksite as part of the main window layout. I use documents (tabs) to partition the user view of the database.

This works great if I bind the DocumentItemsSource to a list of view models, each of which contains data relevant to a particular partition. However, I have found that this doesn't play nicely with the deserialisation behaviour: A call to Deserialize modifies the backing list of VMs by removing those not present in the layout information.

The problem shows up if the database is modified while the app is not running, or if I connect the app to a different database which has a different list of partitions.

Is there a way I can have the layout serialisation only apply to documents that are present, and leave the extra items?

Here is a simplified example of what I'm doing (some boilerplate - like constructor with only InitializeComponent() - excluded):

MainWindowVM.cs

using GalaSoft.MvvmLight;

MainWindowVm : ViewModelBase
{
  MainWindowVm()
  {
    DocumentVms.Add(new OverallViewModel());
    using (var ctx = new MyDataContext())
      foreach (var dataSet in ctx.mainPartitions)
        DocumentVms.Add(new MyPartitionVm(dataSet));
  }

  ObservableCollection<ViewModelBase> _documentVms = ObservableCollection<ViewModelBase>();
  ObservableCollection<ViewModelBase> DocumentVms
  {
    get => _documentVms;
    set => Set(ref _documentsVms, value);
  }
}

 MainWindow.xaml docksite definition:

<docking:DockSite x:Name="dockSite" 
                          AreNewTabsInsertedBeforeExistingTabs="False"
                          DocumentItemsSource="{Binding DocumentVms}">
  <docking:DockSite.Resources>
    <DataTemplate DataType="{x:Type vm:OverallViewModel}">
      <vw:OverallView/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:PartitionVm}">
      <vw:PartitionView/>
    </DataTemplate>
  </docking:DockSite.Resources>
  <docking:DockSite.DocumentItemContainerStyle>
    <Style TargetType="{x:Type docking:DocumentWindow}">
      <Setter Property="IsOpen" Value="True"/>
      <Setter Property="Title" Value="{Binding Title}"/>
      <Setter Property="CanClose" Value="False"/>
      <Setter Property="SerializationId" Value="{Binding Title}"/>
    </Style>
  </docking:DockSite.DocumentItemContainerStyle>
  <!-- Workspace -->
  <docking:Workspace >
    <docking:TabbedMdiHost >
      <docking:TabbedMdiContainer x:Name="tabs">
      </docking:TabbedMdiContainer>
    </docking:TabbedMdiHost>
  </docking:Workspace>
</docking:DockSite>

 MainWindow.xaml.cs

MainWindow : Window
{
  private void Window_Loaded(object sender, RoutedEventArgs e)
  {
    var serializer = ServiceManager.Resolve<DockSiteLayoutSerializer>();
    serializer.SerializationBehavior = DockSiteSerializationBehavior.All;
    serializer.DocumentWindowDeserializationBehavior = DockingWindowDeserializationBehavior.LazyLoad;
    serializer.ToolWindowDeserializationBehavior = DockingWindowDeserializationBehavior.LazyLoad;

    if (File.Exists("SerializationTest.xml"))
      serializer.LoadFromFile("SerializationTest.xml", dockSite);
  }

  private void Window_Closing(object sender, CancelEventArgs e)
  {
    var serializer = ServiceManager.Resolve<DockSiteLayoutSerializer>();
    serializer.SerializationBehavior = DockSiteSerializationBehavior.All;
    serializer.DocumentWindowDeserializationBehavior = DockingWindowDeserializationBehavior.LazyLoad;
    serializer.ToolWindowDeserializationBehavior = DockingWindowDeserializationBehavior.LazyLoad;
    serializer.SaveToFile("SerializationTest.xml", dockSite);
  }
}

 App.xaml.cs:

public partial class App : Application
{
  private async void Application_Startup(object sender, StartupEventArgs e)
  {
    var mainWnd = new MainWindow();
    mainWnd.DataContext = new MainWindowVm()
    mainWnd.Show();
  }
}

 Thanks in advance!


Comments (4)

Posted 3 months ago by Actipro Software Support - Cleveland, OH, USA

Hi Barry,

Can you put together a new simple sample project that shows this happening and send that to our support address?  We'll debug with that and can use it to make sure that any code changes we make help your specific situation.  In your e-mail, please reference this thread, remove the bin/obj folders from the ZIP you send, and change the .zip file extension so it doesn't get spam blocked.  Thanks!


Actipro Software Support
Posted 3 months ago by Actipro Software Support - Cleveland, OH, USA

Hi Barry,

Thanks for the sample.  What happens is that the layout deserialization code closes out the entire layout when a new layout is loaded to ensure that everything loads as intended.  The process of closing a document window will also destroy it (remove it from the DockSite, the process of which also alters your DocumentsItemSource here) by default.  

To work around this, you can set DockSite.AreDocumentWindowsDestroyedOnClose to false.  That will prevent the documents from destroying themselves by default.  This means that you will need to handle the DockSite.WindowsClosed event and call Destroy() on closed DocumentWindows when not doing a layout load.  You could set a flag during layout load that you'd examine in that event handler to know if you are loading a layout or not.

Then also right after you call serializer.LoadFromFile(), you'd add this code:

foreach (var documentWindow in dockSite.DocumentWindows) {
	if (!documentWindow.IsOpen)
		documentWindow.Open();
}

That code effectively sees which documents are still registered but not open (i.e. your partition ones) and ensures they are opened to a default location.

We don't currently have an option to leave documents open that aren't present in the layout, but that's a great idea.  We're going to add a serializer.CanKeepExistingDocumentWindowsOpen property in the next build that will do that.

[Modified 3 months ago]


Actipro Software Support
Posted 3 months ago by Barry smith

Great, thanks! I had to also add an extra line to ensure the new windows are not floating - otherwise if the deserializer sets the active window to a floating window, all the reopened windows are with it instead of in the main tab.

foreach (var documentWindow in dockSite.DocumentWindows) {
	if (!documentWindow.IsOpen) {
		documentWindow.Open();
                documentWindow.IsFloating = false;
        }
}

 Instead of adding the event handlers suggested, would it suffice to change the AreDocumentWindowsDestroyedOnClose only while I deserialise and reopen the windows?

dockSite.AreDocumentWindowsDestroyedOnClose = false;

// Deserialise then reopen any closed windows

dockSite.AreDocumentWindowsDestroyedOnClose = true;
Posted 3 months ago by Actipro Software Support - Cleveland, OH, USA

Hi Barry,

Very good point.  We're able to use an internal method where we can specify the primary dock host as a dock target to prevent this when re-opening the document window.

And yes, the workaround you mentioned for not having to do an event handler should work too.


Actipro Software Support
Information The latest build of this product (2018.1 build 0672) was released 27 days ago, which was after the last post in this thread.

Add a Comment

Please log in to a validated account to post comments.