Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Null Reference Exception inserting documents when isActive is set before insertion #1481

Open
michelle-barnett-89 opened this issue Apr 18, 2019 · 6 comments

Comments

@michelle-barnett-89
Copy link

michelle-barnett-89 commented Apr 18, 2019

I have an application using the AvalonDock library, and to fix an issue with new documents not showing content correctly (#1480), we have to set the IsActive property on the LayoutDocument to true in the BeforeInsertDocument method of the ILayoutUpdateStrategy implementation.

With this setup, the library throws a Null Reference Exception under these circumstances:

  1. Open enough new documents such that the tab headers fill the available width of the screen.
  2. The next document you open will be added at the start of the list instead of the end
  3. Click inside the document to give it focus
  4. Open another new document
  5. AvalonDock throws a Null Reference Exception

I have been able to produce a sample application that produces the same error; which is simply the DocumentOpenSample modified to set the IsActive property as described.
DocumentOpenSample.zip

Is there a fix or work around for this? I cannot simply remove setting the IsActive property, as that causes the previously mentioned bug.

@Dirkster99
Copy link

Hi, I've looked into this and can confirm the problem - also found that it occurs with my stabilized version of AvalonDock

  • without looking deeper, this seems to be a new critical issue
    -> see list of known criticals at the bottom of this page

I'll be trying to debug and hope to find a fix when my time permits
but can't really promise anything without knowing more details ...

@Dirkster99
Copy link

Here is the stacktrace I got:

System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=Xceed.Wpf.AvalonDock
StackTrace:
at Xceed.Wpf.AvalonDock.Controls.DocumentPaneTabPanel.ArrangeOverride(Size finalSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at System.Windows.Controls.Grid.ArrangeOverride(Size arrangeSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at System.Windows.Controls.Grid.ArrangeOverride(Size arrangeSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at System.Windows.Controls.Control.ArrangeOverride(Size arrangeBounds)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at System.Windows.Controls.Grid.ArrangeOverride(Size arrangeSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at MS.Internal.Helper.ArrangeElementWithSingleChild(UIElement element, Size arrangeSize)
at System.Windows.Controls.ContentPresenter.ArrangeOverride(Size arrangeSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at System.Windows.Controls.Grid.ArrangeOverride(Size arrangeSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at System.Windows.Controls.Border.ArrangeOverride(Size finalSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at System.Windows.Controls.Control.ArrangeOverride(Size arrangeBounds)
at Xceed.Wpf.AvalonDock.DockingManager.ArrangeOverride(Size arrangeBounds)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at System.Windows.Controls.Grid.ArrangeOverride(Size arrangeSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at MS.Internal.Helper.ArrangeElementWithSingleChild(UIElement element, Size arrangeSize)
at System.Windows.Controls.ContentPresenter.ArrangeOverride(Size arrangeSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at System.Windows.Documents.AdornerDecorator.ArrangeOverride(Size finalSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at System.Windows.Controls.Border.ArrangeOverride(Size finalSize)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at System.Windows.Window.ArrangeOverride(Size arrangeBounds)
at System.Windows.FrameworkElement.ArrangeCore(Rect finalRect)
at System.Windows.UIElement.Arrange(Rect finalRect)
at System.Windows.ContextLayoutManager.UpdateLayout()
at System.Windows.UIElement.UpdateLayout()
at System.Windows.Controls.TabItem.OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
at System.Windows.UIElement.OnPreviewGotKeyboardFocusThunk(Object sender, KeyboardFocusChangedEventArgs e)
at System.Windows.Input.KeyboardFocusChangedEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
at System.Windows.Input.InputManager.ProcessStagingArea()
at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
at System.Windows.Input.KeyboardDevice.TryChangeFocus(DependencyObject newFocus, IKeyboardInputProvider keyboardInputProvider, Boolean askOld, Boolean askNew, Boolean forceToNullIfFailed)
at System.Windows.Input.KeyboardDevice.Focus(DependencyObject focus, Boolean askOld, Boolean askNew, Boolean forceToNullIfFailed)
at System.Windows.Input.KeyboardDevice.Focus(IInputElement element)
at System.Windows.UIElement.Focus()
at System.Windows.Controls.TabItem.SetFocus()
at System.Windows.Controls.TabControl.OnSelectionChanged(SelectionChangedEventArgs e)
at Xceed.Wpf.AvalonDock.Controls.LayoutDocumentPaneControl.OnSelectionChanged(SelectionChangedEventArgs e)
at System.Windows.Controls.Primitives.Selector.InvokeSelectionChanged(List1 unselectedInfos, List1 selectedInfos)
at System.Windows.Controls.Primitives.Selector.SelectionChanger.End()
at System.Windows.Controls.Primitives.Selector.SetSelectedHelper(Object item, FrameworkElement UI, Boolean selected)
at System.Windows.Controls.Primitives.Selector.NotifyIsSelectedChanged(FrameworkElement container, Boolean selected, RoutedEventArgs e)
at System.Windows.Controls.Primitives.Selector.OnSelected(Object sender, RoutedEventArgs e)
at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
at System.Windows.Controls.TabItem.OnSelected(RoutedEventArgs e)
at System.Windows.Controls.TabItem.OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
at System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
at System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
at System.Windows.StyleHelper.ApplyStyleOrTemplateValue(FrameworkObject fo, DependencyProperty dp)
at System.Windows.StyleHelper.InvalidateContainerDependents(DependencyObject container, FrugalStructList1& exclusionContainerDependents, FrugalStructList1& oldContainerDependents, FrugalStructList1& newContainerDependents) at System.Windows.StyleHelper.DoStyleInvalidations(FrameworkElement fe, FrameworkContentElement fce, Style oldStyle, Style newStyle) at System.Windows.StyleHelper.UpdateStyleCache(FrameworkElement fe, FrameworkContentElement fce, Style oldStyle, Style newStyle, Style& styleCache) at System.Windows.FrameworkElement.OnStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) at System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e) at System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e) at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args) at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType) at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal) at System.Windows.Controls.ItemsControl.ApplyItemContainerStyle(DependencyObject container, Object item) at System.Windows.Controls.ItemsControl.MS.Internal.Controls.IGeneratorHost.PrepareItemContainer(DependencyObject container, Object item) at System.Windows.Controls.ItemContainerGenerator.System.Windows.Controls.Primitives.IItemContainerGenerator.PrepareItemContainer(DependencyObject container) at System.Windows.Controls.Panel.AddChildren(GeneratorPosition pos, Int32 itemCount) at System.Windows.Controls.Panel.OnItemsChangedInternal(Object sender, ItemsChangedEventArgs args) at System.Windows.Controls.Panel.OnItemsChanged(Object sender, ItemsChangedEventArgs args) at System.Windows.Controls.ItemContainerGenerator.OnItemAdded(Object item, Int32 index) at System.Windows.Controls.ItemContainerGenerator.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args) at System.Windows.WeakEventManager.ListenerList1.DeliverEvent(Object sender, EventArgs e, Type managerType)
at System.Windows.WeakEventManager.DeliverEvent(Object sender, EventArgs args)
at System.Collections.Specialized.CollectionChangedEventManager.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
at System.Windows.Data.CollectionView.OnCollectionChanged(NotifyCollectionChangedEventArgs args)
at System.Windows.Controls.ItemCollection.OnViewCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
at System.Windows.WeakEventManager.ListenerList1.DeliverEvent(Object sender, EventArgs e, Type managerType) at System.Windows.WeakEventManager.DeliverEvent(Object sender, EventArgs args) at System.Collections.Specialized.CollectionChangedEventManager.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args) at System.Windows.Data.CollectionView.OnCollectionChanged(NotifyCollectionChangedEventArgs args) at System.Windows.Data.ListCollectionView.ProcessCollectionChangedWithAdjustedIndex(NotifyCollectionChangedEventArgs args, Int32 adjustedOldIndex, Int32 adjustedNewIndex) at System.Windows.Data.ListCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args) at System.Windows.Data.CollectionView.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args) at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e) at System.Collections.ObjectModel.ObservableCollection1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
at System.Collections.ObjectModel.ObservableCollection1.InsertItem(Int32 index, T item) at System.Collections.ObjectModel.Collection1.Add(T item)
at Xceed.Wpf.AvalonDock.DockingManager.documentsSourceElementsChanged(Object sender, NotifyCollectionChangedEventArgs e)
at System.Collections.ObjectModel.ReadOnlyObservableCollection1.OnCollectionChanged(NotifyCollectionChangedEventArgs args) at System.Collections.ObjectModel.ReadOnlyObservableCollection1.HandleCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
at System.Collections.ObjectModel.ObservableCollection1.OnCollectionChanged(NotifyCollectionChangedEventArgs e) at System.Collections.ObjectModel.ObservableCollection1.InsertItem(Int32 index, T item)
at System.Collections.ObjectModel.Collection`1.Add(T item)
at DocumentOpenSample.WorkSpace.OnNew(Object parameter) in \DocumentOpenSample\DocumentOpenSample\DocumentOpenSample\WorkSpace.cs:line 71
at DocumentOpenSample.RelayCommand.Execute(Object parameter) in \DocumentOpenSample\DocumentOpenSample\DocumentOpenSample\RelayCommand.cs:line 41
at MS.Internal.Commands.CommandHelpers.CriticalExecuteCommandSource(ICommandSource commandSource, Boolean userInitiated)
at System.Windows.Controls.MenuItem.InvokeClickAfterRender(Object arg)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.DispatcherOperation.InvokeImpl()
at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
at System.Windows.Threading.DispatcherOperation.Invoke()
at System.Windows.Threading.Dispatcher.ProcessQueue()
at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
at System.Windows.Application.RunDispatcher(Object ignore)
at System.Windows.Application.RunInternal(Window window)
at System.Windows.Application.Run(Window window)
at System.Windows.Application.Run()
at DocumentOpenSample.App.Main()

@Dirkster99
Copy link

I found the source of the problem and the fix does not appear to be difficult either :-) I located the problem in Xceed.Wpf.AvalonDock.Controls.DocumentPaneTabPanel class at line 54 where the following code crashes at the line where the if statement checks

  • the layoutContent with
  • if( layoutContent.IsSelected && !doc.IsVisible )

Here is the listing of the offending code and a fix is shown below:

protected override Size ArrangeOverride( Size finalSize )
{
  var visibleChildren = Children.Cast<UIElement>().Where( ch => ch.Visibility != System.Windows.Visibility.Collapsed );
  var offset = 0.0;
  var skipAllOthers = false;
  foreach( TabItem doc in visibleChildren )
  {
    var layoutContent = doc.Content as LayoutContent;

    if( skipAllOthers || offset + doc.DesiredSize.Width > finalSize.Width )
    {
      if( layoutContent.IsSelected && !doc.IsVisible )
      {
        var parentContainer = layoutContent.Parent as ILayoutContainer;
        var parentSelector = layoutContent.Parent as ILayoutContentSelector;
        var parentPane = layoutContent.Parent as ILayoutPane;
        int contentIndex = parentSelector.IndexOf( layoutContent );
        if( contentIndex > 0 &&
            parentContainer.ChildrenCount > 1 )
        {
          parentPane.MoveChild( contentIndex, 0 );
          parentSelector.SelectedContentIndex = 0;
          return ArrangeOverride( finalSize );
        }
      }
      doc.Visibility = System.Windows.Visibility.Hidden;
      skipAllOthers = true;
    }
    else
    {
      doc.Visibility = System.Windows.Visibility.Visible;
      doc.Arrange( new Rect( offset, 0.0, doc.DesiredSize.Width, finalSize.Height ) );
      offset += doc.ActualWidth + doc.Margin.Left + doc.Margin.Right;
    }
  }
  return finalSize;

}

A fix is to insert these two lines in the foreach loop to continue looping if the layoutContent appears to be null (see below). I attach a source version with the fix and wanted to ask if you could test and verify that it really fixes your problem without introducing new issues - I'll be testing as well and if thats going good we'll have another fix to be applied here ;-)

DocumentOpenSample.zip

    if (layoutContent == null)
      continue;

protected override Size ArrangeOverride( Size finalSize )
{
  var visibleChildren = Children.Cast<UIElement>().Where( ch => ch.Visibility != System.Windows.Visibility.Collapsed );
  var offset = 0.0;
  var skipAllOthers = false;
  foreach( TabItem doc in visibleChildren )
  {
    var layoutContent = doc.Content as LayoutContent;

    if (layoutContent == null)
      continue;

    if( skipAllOthers || offset + doc.DesiredSize.Width > finalSize.Width )
    {
      if( layoutContent.IsSelected && !doc.IsVisible )
      {
        var parentContainer = layoutContent.Parent as ILayoutContainer;
        var parentSelector = layoutContent.Parent as ILayoutContentSelector;
        var parentPane = layoutContent.Parent as ILayoutPane;
        int contentIndex = parentSelector.IndexOf( layoutContent );
        if( contentIndex > 0 &&
            parentContainer.ChildrenCount > 1 )
        {
          parentPane.MoveChild( contentIndex, 0 );
          parentSelector.SelectedContentIndex = 0;
          return ArrangeOverride( finalSize );
        }
      }
      doc.Visibility = System.Windows.Visibility.Hidden;
      skipAllOthers = true;
    }
    else
    {
      doc.Visibility = System.Windows.Visibility.Visible;
      doc.Arrange( new Rect( offset, 0.0, doc.DesiredSize.Width, finalSize.Height ) );
      offset += doc.ActualWidth + doc.Margin.Left + doc.Margin.Right;
    }
  }
  return finalSize;

}

@Dirkster99
Copy link

On a second thought about this - there is a much better fix that lets the rest of the code work as before but won't crash on your test case :-) So, the better solution is to move the layoutContent variable inside the if statement (because its only used there) and use a boolean to determine whether the layoutContent is selected or not:

Any opionions on this?
DocumentOpenSample_V2.zip

    protected override Size ArrangeOverride( Size finalSize )
    {
      var visibleChildren = Children.Cast<UIElement>().Where( ch => ch.Visibility != System.Windows.Visibility.Collapsed );
      var offset = 0.0;
      var skipAllOthers = false;
      foreach( TabItem doc in visibleChildren )
      {
        if( skipAllOthers || offset + doc.DesiredSize.Width > finalSize.Width )
        {
          bool isLayoutContentSelected = false;
          var layoutContent = doc.Content as LayoutContent;

          if (layoutContent != null)
            isLayoutContentSelected = layoutContent.IsSelected;

          if (isLayoutContentSelected && !doc.IsVisible )
          {
            var parentContainer = layoutContent.Parent as ILayoutContainer;
            var parentSelector = layoutContent.Parent as ILayoutContentSelector;
            var parentPane = layoutContent.Parent as ILayoutPane;
            int contentIndex = parentSelector.IndexOf( layoutContent );
            if( contentIndex > 0 &&
                parentContainer.ChildrenCount > 1 )
            {
              parentPane.MoveChild( contentIndex, 0 );
              parentSelector.SelectedContentIndex = 0;
              return ArrangeOverride( finalSize );
            }
          }
          doc.Visibility = System.Windows.Visibility.Hidden;
          skipAllOthers = true;
        }
        else
        {
          doc.Visibility = System.Windows.Visibility.Visible;
          doc.Arrange( new Rect( offset, 0.0, doc.DesiredSize.Width, finalSize.Height ) );
          offset += doc.ActualWidth + doc.Margin.Left + doc.Margin.Right;
        }
      }
      return finalSize;

    }

@Dirkster99
Copy link

Dirkster99 commented Apr 19, 2019

Solution is available in Version 3.5.3 on Nuget
https://www.nuget.org/packages/Dirkster.AvalonDock/

@XceedBoucherS
Copy link
Collaborator

This will be fixed in v3.9.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants