MVVM serialization

Docking/MDI for WPF Forum

Posted 3 years ago by Martin Votruba
Version: 17.1.0650
Avatar

Hello, I am trying to serialize layout created using MVVM. I was following instructions mentioned here, but when I set the DataContext of newly created windows, the view is not updated and I end up with empty Document Window. Can you help me suggesting, what am I doing wrong? 

     class SerializationManager
    {
        public static void Serialize(DockSite dockSite, DockSiteViewModel dockSiteViewModel)
        {
            var layoutSerializer = new DockSiteLayoutSerializer
            {
                SerializationBehavior = DockSiteSerializationBehavior.All,
                DocumentWindowDeserializationBehavior = DockingWindowDeserializationBehavior.AutoCreate
            };
            layoutSerializer.SaveToFile(ConfigurationHelper.Instance.PathToLayoutConfiguration, dockSite);
            using (var fs = new FileStream(ConfigurationHelper.Instance.PathToModel, FileMode.Create))
            {
                var serializer = new DataContractSerializer(typeof(DataItemContract[]));
                var ser = dockSiteViewModel.DocumentItems.Select(i => i.ToContract()).ToArray();
                serializer.WriteObject(fs,ser);
                fs.Close();
            }
        }

        public static void Deserialize(DockSite    dockSite)
        {
            if (!File.Exists(ConfigurationHelper.Instance.PathToModel)||!File.Exists(ConfigurationHelper.Instance.PathToLayoutConfiguration))
                return;
            DataItemContract[] ser;
            using (var fs = new FileStream(ConfigurationHelper.Instance.PathToModel, FileMode.Open))
            {
                var serializer = new DataContractSerializer(typeof(DataItemContract[]));
                ser = (DataItemContract[]) serializer.ReadObject(fs);
                fs.Close();
            }
            var layoutSerializer = new DockSiteLayoutSerializer
            {
                SerializationBehavior = DockSiteSerializationBehavior.All,
                DocumentWindowDeserializationBehavior = DockingWindowDeserializationBehavior.AutoCreate
            };
            layoutSerializer.DockingWindowDeserializing += async (sender, args) =>
            {
                var window = args.Window as DocumentWindow;
                var xmlWindow = args.Node;
                if (window == null || xmlWindow == null) return;
                var dataItemContract = ser.FirstOrDefault(i => i.SerializationId == xmlWindow.SerializationId);
                if (dataItemContract == null) return;
                var viewModel = await dataItemContract.GetViewModel();
                Application.Current.Dispatcher.Invoke(() => window.DataContext = viewModel);
            };
            layoutSerializer.LoadFromFile(ConfigurationHelper.Instance.PathToLayoutConfiguration, dockSite);
        }
    }

Comments (5)

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

Hi Martin,

At a glance, that logic looks like it should be working ok.  Did you put breakpoints on where you set the DataContext to ensure that the proper window is receiving a valid view model? 

Are you using DataTemplates for the DocumentWindows?  If so, how are you setting up the DataTemplates to be applied?  For instance, did you make DataTemplates that are in Application.Resources and implicit target certain VM types?


Actipro Software Support

Posted 3 years ago by Martin Votruba
Avatar

Hi,

yes I have set a breakpoint at the end of the lambda expression to ensure the DataContext is set. The DataContext property of DocumentWindow was set, but the Title was not changed.

I a using DataTempletes, which are defined in App.xaml, here is the code:

<ResourceDictionary>
    <DataTemplate DataType="{x:Type documents:GraphDocumentItemViewModel}">
        <documents:GraphDocumentItemView/>
    </DataTemplate>

    <DataTemplate DataType="{x:Type documents:TableDocumentItemViewModel}">
        <documents:TableDocumentItemView />
    </DataTemplate>
</ResourceDictionary>

 

Also there is example how I have the DockSite defined. The DocumentItems collection is defined in the SiteViewModel.

<docking:DockSite Grid.Column="1" x:Name="dockSite" DataContext="{Binding SiteViewModel}"
                DocumentItemsSource="{Binding DocumentItems}"
                DocumentItemContainerStyle="{StaticResource DocumentWindowStyle}" CanDocumentWindowsFloat="True" >

    <docking:SplitContainer Orientation="Vertical" >
        <docking:Workspace >
            <docking:TabbedMdiHost  TabOverflowBehavior="Scroll"/>
        </docking:Workspace>

    </docking:SplitContainer>
</docking:DockSite>

 

Any suggestions, what might be wrong?


Thank you very much!

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

Hi Martin,

This sounds like something we'd have to debug to sort out.  Could you please put together a new simple sample project that shows it happening and send that to our support address?  In your email, reference this thread and be sure to rename the .zip file extension so it doesn't get spam blocked.  Once we have that, we'll step through and see what's going on and make any code changes needed to help.  Thanks!


Actipro Software Support

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

Hi Martin,

Thank you for the sample.

What's happening here is that you are originally creating your document windows using MVVM via DockSite.DocumentItemsSource, which means you activate our ItemsControl-like features where DocumentWindow "container" objects are generated and DockSite.DocumentItemContainerStyle is applied to them. That works initially.

When you do a layout restore later, you have AutoCreate mode on. This means that as a document window is restored from the layout data, it will create a blank DocumentWindow. Since that was created outside of MVVM mode, no "container" features are activated and DockSite.DocumentItemContainerStyle is not applied, etc.

What you probably want to do instead is something like this, which uses LazyLoad and loads the VM on-demand with the docking window initially closed. It will be opened by our deserialization code that follows this event call.

var layoutSerializer = new DockSiteLayoutSerializer
{
    SerializationBehavior = DockSiteSerializationBehavior.All,
    DocumentWindowDeserializationBehavior = DockingWindowDeserializationBehavior.LazyLoad
};
layoutSerializer.DockingWindowDeserializing += (sender, args) =>
{
	var dockSiteVM = (DockSiteViewModel)dockSite.DataContext;

	// TODO: Make sure each docking window has a SerializationId or other info store
	//  that allows you to determine what kind of VM to load up here... this is a test
	//  of basic functionality though to show you how to load a VM-based docking window 
	//  when deserializaing a layout
	var windowVM = new Documents.GraphDocumentItemViewModel();
	windowVM.IsOpen = false; 
	windowVM.SerializationId = args.Node.SerializationId;
	dockSiteVM.DocumentItems.Add(windowVM);

	args.Window = dockSite.ContainerFromDocumentItem(windowVM);
};
layoutSerializer.LoadFromFile(ConfigurationHelper.Instance.PathToLayoutConfiguration, dockSite);

I hope that helps.  Please let us know if you have any further questions.


Actipro Software Support

Posted 2 years ago by Martin Votruba
Avatar

Thank you, now it works! :)

The latest build of this product (v2019.1 build 0683) was released 1 month ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.