Avalondock nullreferenceexception when restoring state with dynamic anchorables

Avalondock nullreferenceexception when restoring state with dynamic anchorables

Hello,

I have the NullReferenceException problem when restoring state again.

I derive my anchorables from this class:


    public abstract class AbstractPanel {

        private Visibility _visibility;

        public virtual string ContentId { get { return GetType().AssemblyQualifiedName; } }

        public abstract string Title { get; }

        public abstract FrameworkElement UIElement { get; }

        public Visibility Visibility {
            get {
                return _visibility;
            }
            set {
                var oldVis = Visibility;
                _visibility = value;
                if(oldVis != Visibility) {
                    OnPropertyChanged("Visibility");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName) {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Right at the start, in my App.xaml.cs, I collect my panels (I am using MEF):


            foreach(var panel in allServices
                .OfType<IPanelComponent>()
                .SelectMany(c => c.Panels)) {
                SimpleIoc.Default.Register<AbstractPanel>(() => panel, panel.ContentId, true);
            }

In my XAML, I registered it correctly:


                    <styles:PanesStyleSelector.AnchorableStyle>
                        <Style TargetType="{x:Type xcad:LayoutAnchorableItem}">
                            <Setter Property="Title" Value="{Binding Model.Title}"/>
                            <Setter Property="ContentId" Value="{Binding Model.ContentId}" />
                            <Setter Property="Visibility" Value="{Binding Model.Visibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                        </Style>
                    </styles:PanesStyleSelector.AnchorableStyle>

Right at the start of the ViewModel, I load the anchorables accordingly:


Anchorables = SimpleIoc.Default.GetAllCreatedInstances<AbstractPanel>().ToArray();

When saving a layout, the generated XML looks OK:


<?xml version="1.0" encoding="utf-16"?>
<LayoutRoot xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <RootPanel Orientation="Horizontal">
    <LayoutAnchorablePane DockWidth="188" FloatingWidth="200" FloatingHeight="519" FloatingLeft="87" FloatingTop="454">
      <LayoutAnchorable AutoHideMinWidth="100" AutoHideMinHeight="100" Title="Measurements" IsSelected="True" ContentId="Measurements" FloatingLeft="87" FloatingTop="454" FloatingWidth="200" FloatingHeight="519" LastActivationTimeStamp="01/26/2017 23:10:05" PreviousContainerId="e19e3aa6-8537-4dca-b9c6-d489b756f92d" PreviousContainerIndex="0" />
    </LayoutAnchorablePane>
    <LayoutPanel Orientation="Horizontal">
      <LayoutDocumentPaneGroup Orientation="Horizontal">
        <LayoutDocumentPane />
      </LayoutDocumentPaneGroup>
    </LayoutPanel>
    <LayoutAnchorablePane Id="e19e3aa6-8537-4dca-b9c6-d489b756f92d" DockWidth="200">
      <LayoutAnchorable AutoHideMinWidth="100" AutoHideMinHeight="100" Title="All Channels" IsSelected="True" ContentId="All Channels" LastActivationTimeStamp="01/26/2017 23:10:04" />
    </LayoutAnchorablePane>
  </RootPanel>
  <TopSide />
  <RightSide />
  <LeftSide />
  <BottomSide />
  <FloatingWindows />
  <Hidden />
</LayoutRoot>

But, when deserializing this (the anchorables list is already populated), I get the NullReferenceException.

What is going wrong here?

__________________________________________________________

Followup:

This is the stack trace:


System.NullReferenceException was unhandled by user code
  HResult=-2147467261
  Message=Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
  Source=Xceed.Wpf.AvalonDock
  StackTrace:
       bei Xceed.Wpf.AvalonDock.Controls.LayoutItem.get_View()
       bei Xceed.Wpf.AvalonDock.DockingManager.RemoveViewFromLogicalChild(LayoutContent layoutContent)
       bei Xceed.Wpf.AvalonDock.DockingManager.DetachAnchorablesSource(LayoutRoot layout, IEnumerable anchorablesSource)
       bei Xceed.Wpf.AvalonDock.DockingManager.OnLayoutChanged(LayoutRoot oldLayout, LayoutRoot newLayout)
       bei Xceed.Wpf.AvalonDock.DockingManager.OnLayoutChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
       bei System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
       bei System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
       bei System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
       bei System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
       bei System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal)
       bei System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
       bei Xceed.Wpf.AvalonDock.DockingManager.set_Layout(LayoutRoot value)
       bei Xceed.Wpf.AvalonDock.Layout.Serialization.XmlLayoutSerializer.Deserialize(TextReader reader)
       bei My.App.ViewModel.Behavior.AvalonDockLayoutSerializer.LoadLayout(DockingManager dm, String layoutXml) in C:\Somewhere\MyApp\ViewModel\Behavior\AvalonDockLayoutSerializer.cs:Zeile 108.
       bei My.App.ViewModel.MainViewModel.<>c__DisplayClass3_1.<LoadLayout>b__1() in C:\Somewhere\MyApp\ViewModel\MainViewModel.cs:Zeile 46.
       bei System.Windows.Threading.DispatcherOperation.InvokeDelegateCore()
       bei System.Windows.Threading.DispatcherOperation.InvokeImpl()
  InnerException: 

I got it to work in the meantime, with a crude hack using reflection.

This is what I do:



        public static void LoadLayout(this DockingManager dm, string layoutXml) {
            // Walk down the layout and gather the LayoutContent elements.
            // AD bails out when it tries to invoke RemoveViewFromLogicalChild
            // on them.
            var l = GatherLayoutContent(dm.Layout).ToArray();
            // Remove the views by force
            foreach(var x in l) {
                dm.GetType()
                    .GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
                    .Where(m => m.Name.Equals("RemoveViewFromLogicalChild"))
                    .First()
                    .Invoke(dm, new object[] { x });
            }
            // Now, the deserialization works
            using(var sr = new StringReader(layoutXml)) {
                var ser = new XmlLayoutSerializer(dm);
                ser.Deserialize(sr);
            }
        }

        private static IEnumerable<LayoutContent> GatherLayoutContent(ILayoutElement le) {
            if(le is LayoutContent) {
                yield return (LayoutContent)le;
            }
            IEnumerable<ILayoutElement> children = new ILayoutElement[0];
            if(le is LayoutRoot) {
                children = ((LayoutRoot)le).Children;
            } else if(le is LayoutPanel) {
                children = ((LayoutPanel)le).Children;
            } else if(le is LayoutDocumentPaneGroup) {
                children = ((LayoutDocumentPaneGroup)le).Children;
            } else if(le is LayoutAnchorablePane) {
                children = ((LayoutAnchorablePane)le).Children;
            } else if(le is LayoutDocumentPane) {
                children = ((LayoutDocumentPane)le).Children;
            }
            foreach(var child in children) {
                foreach(var x in GatherLayoutContent(child)) {
                    yield return x;
                }
            }
        }

Needless to say, I want to get rid of this hack. So, did I find a bug or am I missing something?