RestoreLayout not restoring hidden DockableContents - Contains AvalonDock bugfix

Jun 29, 2009 at 2:47 AM

Hello,

AvalonDock is an excellent library!  Thanks for creating it.  I'm using version 1.2.2262.

I think I'm doing something wrong when I try to restore my layout.  For testing I start with a DocumentPane in a ResizingPanel in a DockingManager.  Then I add two DockableContent controls, each in its own DockablePane, and those are docked to the right side.  Then I manually drag one of them to be docked on the bottom.  Then I hide/close the one DockableContent that's still docked on the right side.  If I choose to reshow the hidden DockableContent here, it works.  Then I close my application.

When I close the application, I call DockingManager.SaveLayout(filename).

When I restart the application, I first reload only the DockableContents that were *not* hidden (in this case, only the one docked to the bottom).  I then call DockingManager.RestoreLayout(filename).  It seems to work (i.e. the bottom DockableContent is visible again).  However, when the user goes to the view menu to display the other content that was docked on the right, nothing happens.  It turns out that the content is there on the right, but you have to resize it to see it.  Somehow the size of the hidden content is not being persisted and restored.

Any ideas?

 - Scott

Jun 29, 2009 at 3:50 PM

Can you post the xaml file of your window?

 

Juergen

Jun 30, 2009 at 1:17 AM
Edited Jun 30, 2009 at 1:22 AM

My xaml file couldn't be simpler (this is in the application resources):

 

    <DataTemplate DataType="{x:Type local:LayoutManager}">
        <ContentControl Content="{Binding Path=DockManager}">
        </ContentControl>
    </DataTemplate>

 

My LayoutManager class has this property:

 

        /// <summary>
        /// The View binds to this property
        /// </summary>
        public DockingManager DockManager
        {
            get
            {
                return m_Content;
            }
        }
        private readonly DockingManager m_Content = new DockingManager();

 

I create a document pane inside a resizing panel in the constructor of the LayoutManager:

 

        private DocumentPane m_docPane = new DocumentPane(); // documents are put in here
        private ResizingPanel m_resizingPanel = new ResizingPanel(); // pads are docked to this

        public LayoutManager()
        {
            // Basic configuration:
            // DocumentPane 
            //   in a ResizingPanel 
            //      in a DockingManager
            m_docPane.Name = "DocumentPane";
            m_resizingPanel.Name = "ResizingPanel";
            m_resizingPanel.Children.Add(m_docPane);
            DockManager.Content = m_resizingPanel;
            
            DockManager.Loaded += new System.Windows.RoutedEventHandler(DockManager_Loaded);
            DockManager.LayoutUpdated += new EventHandler(DockManager_LayoutUpdated);
        }

I have a method on LayoutManager called ShowPad (I stole the term "pad" from SharpDevelop - basically it's the ViewModel for DockableContent and I use DataTemplates to pair up a "View" to each pad):

        public void ShowPad(IPad pad)
        {
            if (!m_padLookup.ContainsKey(pad))
            {
                DockableContent content = new DockableContent();
                content.Content = pad;
                content.Title = pad.Title;
                content.Name = pad.Name;
                m_padLookup.Add(pad, content);
                DockablePane dp = new DockablePane();
                dp.Items.Add(content);
                m_resizingPanel.Children.Add(dp);
            }
            DockManager.Show(m_padLookup[pad]);
        }

And m_padLookup is a Dictionary<IPad, DockableContent>. That just keeps me from showing the same one twice.

My main window has an OnClosing event handler:

        public void OnClosing(object sender, EventArgs e)
        {
            List<string> padNamesList = new List<string>();
            foreach (IPad pad in LayoutManager.Pads)
            {
                if (LayoutManager.IsVisible(pad))
                {
                    padNamesList.Add(pad.Name);
                }
            }
            string padNames = String.Join(",", padNamesList.ToArray());
            File.WriteAllText("PadsSave.txt", padNames);
            LayoutManager.SaveLayout("LayoutManagerSave.xml");
        }
In LayoutManager, here's the IsVisible and SaveLayout methods/functions:

        public bool IsVisible(IPad pad)
        {
            if (m_padLookup.ContainsKey(pad))
            {
                DockableContent content = m_padLookup[pad];
                return (content.State != DockableContentState.Hidden);
            }
            else
            {
                return false;
            }
        }

        public void SaveLayout(string filename)
        {
            if (DockManager.IsLoaded)
            {
                DockManager.SaveLayout(filename);
            }
        }

Oh, and LayoutManager.Pads just returns the m_padLookup.Keys collection.

I pass through the DockingManager.Loaded event and raise it as the Loaded event of the LayoutManager. Then in the main window, I hook that event to restore the layout like this:

            LayoutManager.Loaded += delegate(object sender, EventArgs e)
                {
                    string padNames = File.ReadAllText("PadsSave.txt");
                    List<string> padNamesList = padNames.Split(new char[] { ',' }).ToList();
                    foreach (IPad p in pads)
                    {
                        if (padNamesList.Contains(p.Name))
                        {
                            LayoutManager.ShowPad(p);
                        }
                    }
                    LayoutManager.RestoreLayout("LayoutManagerSave.xml");
                };


 

Jul 1, 2009 at 8:40 PM

Hi,

This is still a major issue for me.  Does anyone have any ideas?

 

Jul 16, 2009 at 3:59 AM

Sorry for the delay on this; I was on vacation.  I was able to solve this problem with a few code changes (posted below).  The problem occurs because when you hide a DockableContent, it detaches it from its parent pane, but stores everything it needs in an internal memento class called DockableContentStateAndPosition.  This class holds a reference to the pane it was hidden from, which child index it was in the pane, the width, height, and anchor style.  The problem is that when you call SaveLayout, it's not saving the contents of this memento object along with the DockableContent XML node in the Hidden section of the XML file.  Likewise, it's not restoring this information when you call RestoreLayout.

First, the changes to DockableContent.cs:

Here's the new DockableContent.SaveLayout method.  It now stores the memento info if available.

 

        public virtual void SaveLayout(XmlWriter storeWriter)
        {
            if (!FloatingWindowSize.IsEmpty)
            {
                storeWriter.WriteAttributeString(
                    "FloatingWindowSize", new SizeConverter().ConvertToInvariantString(FloatingWindowSize));
            }
            if (SavedStateAndPosition != null)
            {
                storeWriter.WriteAttributeString(
                    "ChildIndex", SavedStateAndPosition.ChildIndex.ToString());
                storeWriter.WriteAttributeString(
                    "Width", SavedStateAndPosition.Width.ToString());
                storeWriter.WriteAttributeString(
                    "Height", SavedStateAndPosition.Height.ToString());
                storeWriter.WriteAttributeString(
                    "Anchor", SavedStateAndPosition.Anchor.ToString());
            }

 

Likewise, the new RestoreLayout method pulls the info back in if it has been saved:

 

        public virtual void RestoreLayout(XmlElement contentElement)
        {
            if (contentElement.HasAttribute("FloatingWindowSize"))
                FloatingWindowSize = (Size)(new SizeConverter()).ConvertFromInvariantString(contentElement.GetAttribute("FloatingWindowSize"));

            if (contentElement.HasAttribute("ChildIndex"))
            {
                _savedStateAndPosition = new DockableContentStateAndPosition(
                    ContainerPane,
                    int.Parse(contentElement.GetAttribute("ChildIndex")),
                    double.Parse(contentElement.GetAttribute("Width")),
                    double.Parse(contentElement.GetAttribute("Height")),
                    (AnchorStyle) Enum.Parse(typeof(AnchorStyle), contentElement.GetAttribute("Anchor"))
                    );
            }
        } 

 

In order to make this work, I had to create another constructor in the DockableContentStateAndPosition internal class:

 

        public DockableContentStateAndPosition(
            Pane containerPane,
            int childIndex,
            double width,
            double height,
            AnchorStyle anchor)
        {
            ContainerPane = containerPane;
            ChildIndex = childIndex;
            Width = width;
            Height = height;
            Anchor = anchor;
        }

 

Unfortunately, DockableContent.RestoreLayout wasn't getting called when restoring the Hidden DockableContent,

so I had to modify this section of DockingManager.RestoreLayout(XmlDocument doc)...

 

            //restore hidden contents
            foreach (XmlElement hiddenContentElement in doc.DocumentElement.ChildNodes[1].ChildNodes)
{
foreach (DockableContent hiddenContent in actualContents)
{
if (hiddenContentElement.GetAttribute("Name") == hiddenContent.Name
&& hiddenContent.State != DockableContentState.Hidden)
{
Hide(hiddenContent);
hiddenContent.RestoreLayout(hiddenContentElement);
}
}
}

Notice the bolded line that had to be added.

I also made one other change (for a related bug) for DockingManager.Show(...), in this snippet:

                if (content.ContainerPane.GetManager() == null)
{
//disconnect the parent pane from previous panel if (content.ContainerPane.Parent != null)
{
((Panel)content.ContainerPane.Parent).Children.Remove(content.ContainerPane);
}
Anchor(content.ContainerPane, desideredAnchor);
}

I was getting errors where the content.ContainerPane.Parent was sometimes null and causing this to crash.

With all of these changes in place, it fixes most of the problems. I still have the same problem if the user drags
the DockableContent into a floating window, then hides it, then after a SaveLayout and RestoreLayout tries to
show that DockableContent again. It just ends up with 0 width on the right side.

 

 

Feb 22, 2013 at 12:36 PM
Edited Feb 22, 2013 at 8:27 PM
hey man..

about this issue of hidding the right panes and never getting them showed again, do you have any tips??

thanks!


Well.. I found a half part solution, at the "____Show(DockableContent content, DockableContentState desideredState, AnchorStyle desideredAnchor)" Method, at this part:
                        content.SetStateToDock();
                        content.Activate();
                        
                        (prevPane.Parent as UIElement).InvalidateMeasure();
change to :
                        content.SetStateToDock();
                        content.Activate();
                        
                        (prevPane.Parent as UIElement).Measure(new Size(content.SavedStateAndPosition.Width, content.SavedStateAndPosition.Height));
But it still get some cases that you can't show the windows again.