locked
VS Shell issue with tool window whose Visual Tree contains null children RRS feed

  • Question

  • I am currently working on a VSPackage for Visual Studio 2010. The Operating System is Windows 7 Enterprise edition and the implementation language is C#. The VSPackage project was generated using the VSPackage template. This VSPackage contains a custom tool window. This tool window contains a third party WPF Control.

    The Visual Tree of this third party control can contain null Visual children within it. From my understanding of the WPF framework, it is ok to have null children within your Visual Tree. The remarks on the Add() method of the VisualCollection class states that: “Adding a Visual whose value is null is permitted and does not raise an exception”. This may apply only to the collection itself and not the visual tree, but I couldn’t find any documentation stating that the visual tree could not contain null children.

    When this tool window is first opened within Visual Studio, it opens and displays without issue. The tool window starts out in the editor area. The window can then be undocked without issue, however when trying to re-dock, a null pointer exception is thrown. Based on the stack trace (snippet attached below) after this exception is thrown, It appears that visual studio is trying to move the focus to the tool window. Visual Studio traverses the visual tree of the Control (using the extension method TraverseVisualTreeReverese<t>()), and at a certain point, the recursive calls run into a null child. The TraverseVisualTreeReverese<t>()) method doesn’t account for this null child and will recurse and then call VisualTreeHelper.GetChilrenCount() on the null reference and the exception is raised (see code below). </t></t>

    My question, is how can I prevent or resolve this issue. I have no ability to alter the third party control.

    // Microsoft.VisualStudio.PlatformUI.ExtensionMethods
    public static void TraverseVisualTreeReverse<T>(this DependencyObject obj, Action<T> action) where T : class {
        for (int i = VisualTreeHelper.GetChildrenCount(obj) - 1; i >= 0; i--)
        {
            DependencyObject child = VisualTreeHelper.GetChild(obj, i);
            T t = child as T;
            child.TraverseVisualTreeReverse(action);
            if (t != null)
            {
                action(t);
            }
        }
    }
    

    PresentationCore.dll!MS.Internal.Media.VisualTreeUtils.AsNonNullVisual(System.Windows.DependencyObject element, out System.Windows.Media.Visual visual, out System.Windows.Media.Media3D.Visual3D visual3D) + 0x4a byte

    PresentationCore.dll!System.Windows.Media.VisualTreeHelper.GetChildrenCount(System.Windows.DependencyObject reference) + 0x1a bytes

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.TraverseVisualTreeReverse<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj = null, System.Action<system.windows.interop.hwndhost>action = {Method = {System.Reflection.RuntimeMethodInfo}}) + 0x26 bytes</system.windows.interop.hwndhost></system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.TraverseVisualTreeReverse<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj = {yWorks.Canvas.CanvasObjectTree.DefaultCanvasObjectGroup.MyVisual}, System.Action<system.windows.interop.hwndhost>action = {Method = {System.Reflection.RuntimeMethodInfo}}) + 0xc4 bytes</system.windows.interop.hwndhost></system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.TraverseVisualTreeReverse<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj = {yWorks.Canvas.CanvasObjectTree.DefaultCanvasObjectGroup.MyVisual}, System.Action<system.windows.interop.hwndhost>action = {Method = {System.Reflection.RuntimeMethodInfo}}) + 0xc4 bytes</system.windows.interop.hwndhost></system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.TraverseVisualTreeReverse<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj = {yWorks.Canvas.CanvasObjectTree.DefaultCanvasObjectGroup.MyVisual}, System.Action<system.windows.interop.hwndhost>action = {Method = {System.Reflection.RuntimeMethodInfo}}) + 0xc4 bytes</system.windows.interop.hwndhost></system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.TraverseVisualTreeReverse<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj = {yWorks.Canvas.CanvasControl.ViewportClass}, System.Action<system.windows.interop.hwndhost>action = {Method = {System.Reflection.RuntimeMethodInfo}}) + 0xc4 bytes</system.windows.interop.hwndhost></system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.TraverseVisualTreeReverse<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj = {yWorks.Canvas.CanvasContentHost}, System.Action<system.windows.interop.hwndhost>action = {Method = {System.Reflection.RuntimeMethodInfo}}) + 0xc4 bytes</system.windows.interop.hwndhost></system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.TraverseVisualTreeReverse<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj = {System.Windows.Controls.Grid}, System.Action<system.windows.interop.hwndhost>action = {Method = {System.Reflection.RuntimeMethodInfo}}) + 0xc4 bytes</system.windows.interop.hwndhost></system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.TraverseVisualTreeReverse<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj = {yWorks.yFiles.UI.GraphControl}, System.Action<system.windows.interop.hwndhost>action = {Method = {System.Reflection.RuntimeMethodInfo}}) + 0xc4 bytes</system.windows.interop.hwndhost></system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.TraverseVisualTreeReverse<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj = {System.Windows.Controls.DockPanel}, System.Action<system.windows.interop.hwndhost>action = {Method = {System.Reflection.RuntimeMethodInfo}}) + 0xc4 bytes</system.windows.interop.hwndhost></system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.TraverseVisualTreeReverse<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj = {System.Windows.Controls.ContentPresenter}, System.Action<system.windows.interop.hwndhost>action = {Method = {System.Reflection.RuntimeMethodInfo}}) + 0xc4 bytes</system.windows.interop.hwndhost></system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.TraverseVisualTreeReverse<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj = {System.Windows.Controls.Border}, System.Action<system.windows.interop.hwndhost>action = {Method = {System.Reflection.RuntimeMethodInfo}}) + 0xc4 bytes</system.windows.interop.hwndhost></system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.TraverseVisualTreeReverse<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj = {SandiaNationalLaboratories.VUseGraphViewer.MyControl}, System.Action<system.windows.interop.hwndhost>action = {Method = {System.Reflection.RuntimeMethodInfo}}) + 0xc4 bytes</system.windows.interop.hwndhost></system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.TraverseVisualTree<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj = {Microsoft.VisualStudio.Platform.WindowManagement.Controls.GenericPaneContentPresenter}, System.Action<system.windows.interop.hwndhost>action = {Method = {System.Reflection.RuntimeMethodInfo}}) + 0xd9 bytes</system.windows.interop.hwndhost></system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.10.0.dll!Microsoft.VisualStudio.PlatformUI.ExtensionMethods.FindDescendants<system.windows.interop.hwndhost>(System.Windows.DependencyObject obj) + 0xb9 bytes</system.windows.interop.hwndhost>

    Microsoft.VisualStudio.Shell.UI.Internal.dll!Microsoft.VisualStudio.PlatformUI.FocusHelper.IsKeyboardFocusWithin(System.Windows.UIElement element) + 0x44 bytes

    Microsoft.VisualStudio.Shell.UI.Internal.dll!Microsoft.VisualStudio.PlatformUI.FocusHelper.MoveFocusInto(System.Windows.UIElement element = {Microsoft.VisualStudio.Platform.WindowManagement.Controls.GenericPaneContentPresenter}) + 0xe bytes

    Microsoft.VisualStudio.Platform.WindowManagement.dll!Microsoft.VisualStudio.Platform.WindowManagement.WindowFrame.RestoreFocusNextLoad.AnonymousMethod__38() + 0x49 bytes

    Wednesday, March 7, 2012 11:32 PM

Answers

  • I can't speak to any patches/service packs for 2010 (there already has been an SP for 2010) as I don't make those decisions. I would strongly suspect there wouldn't be a stand-alone patch as the cost of doing that is very high and thus the problems they fix have to impact a significant number of people. I haven't heard of plans for another SP for 2010, but that doesn't mean much as I am not involved in those kinds of planning activities.

    I can say that it is fixed in vNext (since I fixed the code the other day after seeing your post), but obviously that has not yet been released nor does this help you :(

    Ryan

    • Marked as answer by Carlos512 Wednesday, March 14, 2012 5:00 PM
    Saturday, March 10, 2012 4:34 PM

All replies

  • Hmmm yes, definetly a bug.  I filed one internally and fixed this code, unfortunately that does not help you here.  Can you make a trivial derivation from the offending control and override its VisualChildrenCount property and GetVisualChild method to filter out null children?

    Ryan

    Thursday, March 8, 2012 8:58 PM
  • Ryan

    I cannot filter out the null children by extending the Control (GraphControl), because the null children occur several levels down the Visual Tree from the Control itself. Most of the Controls descendants are internal classes for which I have no access. I did try to traverse the Visual Tree myself, then reflectively remove null children from any and all Visual objects. I used the following code:

      static class NullChildFilter
      {
        private static MethodInfo removeMethod;
        static NullChildFilter()
        {
          removeMethod = typeof(Visual).GetMethod("RemoveVisualChild", BindingFlags.NonPublic | BindingFlags.Instance);
        }
    
        public static void FilterOutNullVisualChildren(this DependencyObject obj)
        {
          int nullCount = 0;
          for(int i = VisualTreeHelper.GetChildrenCount(obj)-1; i>=0; i--)
          {
            DependencyObject child = VisualTreeHelper.GetChild(obj, i);
            if(child == null)
            {
              nullCount++;
            }
            else
            {
              child.FilterOutNullVisualChildren();
            }
          }
    
          Visual visual = obj as Visual;
          if(visual != null)
            for(int i=0; i<nullCount; i++)
            {
              try
              {
                removeMethod.Invoke(visual, new Visual[] { null });
              }
              catch(Exception e)
              {
                Console.WriteLine(e.Message);
              }
            }
        }
      }

    I have no way of accessing the actual collection of Visuals so I hoped that calling RemoveVisualChild() with a null argument would do the trick, but this failed to produce results. 

    thank you for your help,

    Carlos

    Friday, March 9, 2012 7:26 PM
  • Yeah I don't believe there is any way to avoid this, it is in core focus restoration logic. This is definetly an odd scenario, I don't know what a null visual child even represents logically. VS 2010 has been released for a couple of years now and I have seen 0 reports of this and 0 Watson buckets of this kind of crash, so I think it may be safe to say you are the very first person (of thousands of extensions) to run into this.

    Ryan

    Friday, March 9, 2012 8:11 PM
  • I feel like one of those people who is the first to ever be diagnosed with a disease. Is it safe for me to assume that this fix will be available with the next update/service pack to Visual Studio? And if so, do you by chance have an approximate time frame for the realse of the update?

    Carlos

    Friday, March 9, 2012 10:00 PM
  • I can't speak to any patches/service packs for 2010 (there already has been an SP for 2010) as I don't make those decisions. I would strongly suspect there wouldn't be a stand-alone patch as the cost of doing that is very high and thus the problems they fix have to impact a significant number of people. I haven't heard of plans for another SP for 2010, but that doesn't mean much as I am not involved in those kinds of planning activities.

    I can say that it is fixed in vNext (since I fixed the code the other day after seeing your post), but obviously that has not yet been released nor does this help you :(

    Ryan

    • Marked as answer by Carlos512 Wednesday, March 14, 2012 5:00 PM
    Saturday, March 10, 2012 4:34 PM