problem restoring layout with prism

Aug 3, 2011 at 8:42 AM

i"m using prism with AvalonDoc.

when i try to restore the layout (on the event handler of the menu button "RestoreLayout")

dockManager.RestoreLayout(FileName);

i do get the correct layout structure, but the content of every region is now empty. do i need to reload to modules or something like that ?

 

the avalondoc code im my  xaml code is something like that:

<ad:DockingManager x:Name="dockManager" Grid.Row="3">

   <ad:ResizingPanel Orientation="Horizontal">

      <ad:ResizingPanel ad:ResizingPanel.ResizeWidth="*" Orientation="Vertical" VerticalAlignment="Top">

       <ad:DockablePane ad:ResizingPanel.ResizeHeight="150" prism:RegionManager.RegionName="RegionDocPane1">

                        <ad:DockableContent Name="DocContent1" HorizontalAlignment="Left"/>

        </ad:DockablePane>

                </ad:ResizingPanel>

<ad:DockablePane ad:ResizingPanel.ResizeWidth="170" prism:RegionManager.RegionName="RegionDocPane2">

                    <ad:DockableContent Name="DocContent2" HorizontalAlignment="Right"/>

</ad:DockablePane>

            </ad:ResizingPanel>

        </ad:DockingManager>

 

 

 

Aug 30, 2011 at 4:19 PM

I have the same problem here. This problem is caused, because AvalonDock has no support for PRISM regions.

Assume you have the following simplified window layout
    ShellView.xaml:
    <ad:DockingManager x:Name="dockManager">
        <ad:ResizingPanel Orientation.Horizontal prism:RegionManager.RegionName="region1" />
    </ad:DockingManager>

and assume furthermore the following view
    SomeView.xaml:
    <ad:DockablePane>
        <ad:DockableContent Name="someDockableContent" HorizontalAlignment="Left"/>
    </ad:DockablePane>

and finally you load SomeView into region1 like this (for example using MEF)
    var regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
    regionManager.RequestNavigate("region1", new Uri(typeof(SomeView).FullName, UriKind.Relative));

Now, if you store your layout using
    myDockingManager.SaveLayout("SerializedLayout.xml");

you get something similar to the following:
    SerializedLayout.xml:
    <DockingManager version="1.3.0">
        <ResizingPanel ResizeWidth="*" ResizeHeight="*" EffectiveSize="800.0,600.0" Orientation="Horizontal">
            <DockablePane ResizeWidth="*" ResizeHeight="*" EffectiveSize="800.0,600.0" ID="..." Anchor="Left" IsAutoHidden="false">
                <DockableContent Name="someDockableContent" FloatingWindowSize="250,400" />
            </DockablePane>
        </ResizingPanel>
        <Hidden />
        <Windows />
    </DockingManager>

and you notice that no information about the region1 will be stored for the ResizingPanel-tag. It is simply for the reason, that AvalonDock has neither any knowledge about PRISM nor about PRISM's regions. Therefore, when de-serializing the layout using
    myDockingManager.RestoreLayout("SerializedLayout.xml");

you get your ResizingPanel and the DockablePane with the DockableContent but the region1 remains empty. Moreover, I'm not sure, whether region1 actually exists, as it has not been attached to any UI element.

You could try to extend the SaveLayout() and RestoreLayout() functions of the DockingManager() but you would not only have to store the names of particular regions attached to Avalon controls, but you would also have to be very careful during the deserialization. You would have to use PRISM mechanisms (like Region.Add(), RegionManager.AddToRegion()) to add the controls from the serialized XML file to the regions each time any control was nested inside a region. I'm afraid this is the only way to make sure that appropriate region adapters are used and all the region behaviors are attached correctly. Proceeding this way means also that you have direct dependencies into the PRISM framework.

Another way would require two things:
    (1) A callback event/delegate called each time a new control has been deserialized, containing (a) a reference to that control and probably (b) a reference to its parent.
    (2) Some kind of a tag. This tag could store the name of the region previously hosted by the deserialized control.
User code would have to register to the callbacks described under (1). Each time the callback is called:
    private void OnNewControlAddedCallback(newAddedControl)
    {
        /* hosting a region */
        if (!string.IsNullOrEmpty(newAddedControl.Tag))
        {
            // somehow add a new region with name newAddedControl.Tag to newAddedControl
        }

        /* hosted inside a region */
        if (!string.IsNullOrEmpty(newAddedControl.Parent.Tag))
        {
            regionManager.Regions[newAddedControl.Parent.Tag].Add(newAddedControl);
        }
    }

I really hope that some AvalonDock developer takes a closer look at this feature request as more and more developers are using PRISM and AvalonDock for their projects.

Hope this helps.

Regards

Sep 7, 2011 at 10:56 AM

Hello all,

 

I faced the same problem and tried to solve it using Dictionary that will add the region each restoreLayout is pressed, it works but it is ineffecient! so i think Extend the AvalonDock SaveLayout is better.

I'm working on it now.

iiS77 does it work with you getting back the modules?

 

Thanks

Sep 7, 2011 at 11:35 AM

In case it's still an issue for you, you should consider voting here (AvalonDock issue tracker) for the issue/proposal that I created.

Regards

Oct 11, 2011 at 5:35 AM

correct me if i'm wrong, but in a mvvvm  enviroment doesn't the ViewModel handle all the loading and layout of the "state" of content?

and the databinding from the ViewModel to the view does the rest.

so if you want to save the state of you region, you have to bind all the layout properties of the eg DockablePane  to a ViewModel poco that you can serialize/deserialize.....

or extend eg the DockablePane  so it has a bindable property that does the serializing/deserializing (eg to xml).

the way the xml loading is implemented now in the dockingmanager is in a "model1" way and could be usefull in a view-first framework.

 

 

 

Oct 11, 2011 at 6:23 AM
Edited Oct 11, 2011 at 1:50 PM

@bram_meijboom: Thank you for your suggestion! I would kindly ask you to give an example ...and to consider the following:

  1. Normally the reason for using PRISM is that neither your Views nor your ViewModels have any clue about PRISM regions. Not at all. Therefore MVVM does not help here in any way.
  2. Storing and restoring the layout properties of AvalonDock (like DockablePane) controls is not a problem.
  3. The problem is to store the PRISM region "bindings". Bindings in the sense, like which region was attached to which AvalonDock control and which Views were connected to which region(s) (please read my earlier post on this matter).

Extending the AvalonDock controls (i.e. DockablePane, DocumentPane, ResizingPanel etc.) with "bindable properties" or callbacks that would be executed while saving and restoring my layout could actually help. I described it as the second alternative in my earlier post. But unfortunately to the best of my knowledge there are no such callbacks (or bindable properties) available. Please correct me if you know some.

Furthermore, right now, I don't see any connections between

  • the way de-/serialization is currently implemented in AvalonDock (in the sense of ViewModel-first or View-first) and
  • the core problem of storing and restoring the hierarchies (or "bindings") of PRISM regions using AvalonDock.

Thanks for your help.

Regards

Oct 16, 2011 at 10:11 AM

well, basicly the serialization is done at the manager level, the manager serializes everything and the rest doesnt know how to do it. What you want is a serialization of a sub part eg resizingPane.serializeXml() resizingPane.deserializeXml() and  so when your region is being initialized you can do a deserialize and the layout of the pane is being restored.

How i would implement the serialization is to make all classes that are used in the layout (the pane's resizers and so on) implement some interface, like toXml(writer) and fromXml(reader) and use that to restore the layout of the region.

The main problem i have noticed using regions bound to sub parts of the dockingmanager is the creation and deletion of resizerPane's. In your example you bind a dockingPane to a region and after dragging/dropping some content into it the herarchy is changed and your bound pane ends up only as a sub part of the "region" you want to use, example:

you start with 

<dockingmanager>
<ResizingPanel Orientation="Vertical">
<DockablePane Name="_leftDockablePane"cal:RegionManager.RegionName="LeftRegion"  />
<DocumentPane Name="_documentPane"cal:RegionManager.RegionName="DocumentRegion" >
... some documents ...
</DocumentPane>
</ResizingPanel>
<dockingmanager>

and after some dragging dropping you end up with: 

<dockingmanager>
<ResizingPanel Orientation="Vertical">
<DockablePane Name="_leftDockablePane" cal:RegionManager.RegionName="LeftRegion"  />
<ResizingPanel Orientation="horizontal">
<DocumentPane Name="_documentPane" cal:RegionManager.RegionName="DocumentRegion" >
... some documents ...
</DocumentPane>
<DocumentPane>
... more documents ...
</DocumentPane>
<DocumentPane>
... event more documents ...
</DocumentPane>
<ResizingPanel>
</ResizingPanel>
<dockingmanager>

so your region ends up to be a subset of what you wanted..  the declaration of the region "should" be at the new created resizer and not the one documentpane at the top and serializing/deserializing would not work for the documentregion.  You prolly need some cusom component/tag for wrapping the region that is inserted into the resizingPane herarchy but is not removed or moved by the Dockingmanager.Anchor method.

you basicly want somethin like this in your xalm 

<dockingmanager>
<ResizingPanel Orientation="Vertical">
<DockablePane Name="_leftDockablePane"cal:RegionManager.RegionName="LeftRegion"  />
<component_that_gets_ignored_by_dockingmanager cal:RegionManager.RegionName="DocumentRegion">
<DocumentPane Name="_documentPane">
... some documents ...
</DocumentPane>
</component_that_gets_ignored_by_dockingmanager>
</ResizingPanel>
<dockingmanager>

and the dockingmanager can happely create/delete pane'sand resizers in the region while you can serialize/deserialize the whole sub-tree contained in the region. But looking at the anchor method that is going to take some work.

Oct 20, 2011 at 9:07 AM

@bram_meijboom: Thank you once again, but I suppose you got me wrong.

The problem is not storing (or restoring) the layout of a region, but to store (or restore) the region itself. Like I wrote before, the problem is not to restore some UI layout using the DockingManager's Restorelayout() function, but to restore the attached properties (like RegionManager.RegionName) for the particular UI elements being deserialized. In fact Dockingmanager cannot restore attached properties of any kind, because DockingManager does not have any knowledge about such properties and that's surely the main issue to solve.

Any thoughts on that?

Oct 22, 2011 at 10:04 PM
Edited Oct 22, 2011 at 10:14 PM

easy enough to do make if you wont mind hacking the Dockingmanager code,

i'd add somenotification to the SaveLayout(XXX) methods so you can add some listeners to regiser different "custom" extensions, something like:

 

public delegate void SaveElementHandler(DependencyObject sender, XmlWriter xmlWriter);

public event SaveElementHandler OnSaveElement;


void SaveLayout(XmlWriter xmlWriter, DockableFloatingWindow flWindow)
{
	xmlWriter.WriteStartElement("FloatingWindow");
        ... all them  attributes...
        OnSaveElement(flwindow, xmlwriter);
	if (this.OnSaveElement != null) {
		OnSaveElement(xmlWriter, flWindow.HostedPane as DockablePane);
	}
	xmlWriter.WriteEndElement();
}

 

 

and then you can add a listener:

 

documentManager.OnSaveElement += delegate(DependencyObject sender, XmlWriter xmlWriter) {
	string name = Regionmanager.GetRegionName(sender);
	if (!String.isEmptyOrwhitespace(name)) {
		/// check if you can add namespace and so on, but not required, prolly can't cause the writer is forwardonly :(
		writer.WriteAttributeString("RegionManager.RegionName", name);
	}
}

 

same goes for the deserialization, prolly somewhere in DocingManager "DockablePane RestoreDockablePaneLayout(....)", add a new event handler with a listener:

OnRestorelayout(contentElement, pane);

 

documentManager.OnRestorelayout += delegate(XmlElement element, DependencyObject dpo) {
	String attributeName= "RegionManager.RegionName";
	XmlAttribute attr = element.getAttribute(attributeName);
	if (attr != null) {
		Regionmanager.SetRegionName(dpo, attr.Value);
	}
}

 

 

least intrusive is copy-past the implementations to a subclass and modify the methods in DockingManager so you can override them. (or use aspects/reflection with no modification).

alas, you get the idea, and looking at the regionmanager code it should work

edit:

would be nice if there is some method that you can register a namespace to a xml serializer/deserializer and all the attached dependancy properties automaticly serialized/deserialized. It should already exist somewhere in the xalm serializers

Nov 2, 2011 at 1:33 PM

I hope I can give your suggestion a try next week. I will also take a closer look at DockingManager code and give you a short feedback.

Thanx!

Jan 27, 2013 at 2:18 PM

Is there any solution now for this issue?

Thank you.

Feb 21, 2013 at 5:14 PM
Hi,

I have been working on a GitHub project to create a participatory IDE framework that uses PRISM, AvalonDock, MahApps Metro etc. Please feel free to look at Wide IDE - comment, participate and contribute to the project if you have time.

The project supports saving and restoring layouts.

Image

Thanks
Chandra