xceedsoftware / wpftoolkit

All the controls missing in WPF. Over 1 million downloads.
Other
3.87k stars 873 forks source link

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

Open michelle-barnett-89 opened 5 years ago

michelle-barnett-89 commented 5 years ago

I have an application using the AvalonDock library, and to fix an issue with new documents not showing content correctly (https://github.com/xceedsoftware/wpftoolkit/issues/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 commented 5 years ago

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

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 commented 5 years ago

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 commented 5 years ago

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

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 commented 5 years ago

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 commented 5 years ago

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

XceedBoucherS commented 5 years ago

This will be fixed in v3.9.