Home › Forums › WPF controls › Other WPF controls › AvalonDock: NullReferenceException when restoring state w/ dynamic Anchorables
Tagged: AvalonDock
-
AuthorPosts
-
#44509 |
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?
Hi,
Can you submit a sample so we can reproduce your issue so that we could have a closer look. You may send it to: support@xceed.com
I just wanted to give a followup that I got it to work now. I didn’t send you demo code because this problem occurred in company code I am not allowed to give away.
I still don’t know what exactly was the culprit, but I redesigned some parts of my application the following way:
– I put my Anchorable-specific stuff in an interface
IPanel
that has all the necessary things like the title, Content ID, and a property referencing aFrameworkElement
that displays the actual panel
– I created anAnchorableViewModel
that has a property which is a reference to myIPanel
and aVisibility
property
– thisAnchorableViewModel
is now in the same assembly as the one where I construct the Dock (my former Panel class I directly used as the view model was defined in another assembly)
– I wire up theAnchorableViewModel
accordingly in XAMLPlus, since I do all my UI stuff highly asynchronously with a lot of async/await and Tasks, and using
Dispatcher.InvokeAsync
a lot to update myObservableCollection
s, I cleaned that part up, too. maybe there was some race condition. -
AuthorPosts
- You must be logged in to reply to this topic.