Adorner layer BUG - more than 144 adorners are not supported

I've encountered a strange problem working with adorners: Somehow adorners are stop working after adding more than 144 adorners to the same adorner layer.

The code below is a custom-panel which simply arrange items like a UniformGrid. It also adds custom Adorner instances (SelectionAdorner) on each added item, which provides visual on item select. The SelectionAdorner holds an IsSelected dependency-property and changes it to true/false on left-mouse-click.The panel also provides several layouts: 1x1, 4x4, etc.

Adding 200 items (or more than 144) the problems beginSmile

I'm working on Windows XP 32bit SP2, Debug and Release.

Here is the code (create new WPF project/files to repro):

(Sorry for not be able to attach files)

Code Snippet

// SelectionAdorner.cs

using System;

using System.Collections.Generic;

using System.Text;

using System.Windows.Input;

using System.Windows;

using System.Windows.Media;

using System.Windows.Documents;

namespace AdornerRendering

{

publicclassSelectionAdorner :Adorner

{

#region Dependency Properties

#region IsSelected Property

///Identifies the IsSelected dependency property

publicstaticreadonlyDependencyProperty IsSelectedProperty =

DependencyProperty.Register("IsSelected",typeof(bool),typeof(SelectionAdorner),

newFrameworkPropertyMetadata(false,FrameworkPropertyMetadataOptions.AffectsRender));

///description for IsSelected property

publicbool IsSelected

{

get {return (bool)GetValue(IsSelectedProperty); }

set { SetValue(IsSelectedProperty,value); }

}

#endregion

#endregion

#region Initialization

public SelectionAdorner(UIElement adornedElement) :base(adornedElement) { }

#endregion

#region Overrides

protectedoverridevoid OnMouseLeftButtonDown(MouseButtonEventArgs e)

{

IsSelected = !IsSelected;

base.OnMouseLeftButtonDown(e);

}

protectedoverridevoid OnRender(DrawingContext drawingContext)

{

Pen bluePen =newPen(Brushes.Blue, 1.0);

bluePen.Freeze();

drawingContext.DrawRectangle(Brushes.Transparent, bluePen,newRect(newPoint(), RenderSize));

if (IsSelected)

{

double thickness = 2.5;

// Draws the Active Border

Pen selectedBorderPen =newPen(Brushes.Red, thickness);

selectedBorderPen.Freeze();

double x = thickness * 0.5;

PathGeometry pathGeometry =newPathGeometry();

double lineLength = RenderSize.Height / 8;

pathGeometry.AddGeometry(newLineGeometry(newPoint(x, 0),newPoint(x, lineLength)));

pathGeometry.AddGeometry(newLineGeometry(newPoint(0, x),newPoint(lineLength, x)));

pathGeometry.AddGeometry(newLineGeometry(newPoint(RenderSize.Width - x, 0),newPoint(RenderSize.Width - x, lineLength)));

pathGeometry.AddGeometry(newLineGeometry(newPoint(RenderSize.Width, x),newPoint(RenderSize.Width - lineLength - x, x)));

pathGeometry.AddGeometry(newLineGeometry(newPoint(0, RenderSize.Height - x),newPoint(lineLength, RenderSize.Height - x)));

pathGeometry.AddGeometry(newLineGeometry(newPoint(x, RenderSize.Height),newPoint(x, RenderSize.Height - lineLength)));

pathGeometry.AddGeometry(newLineGeometry(newPoint(RenderSize.Width - x, RenderSize.Height),newPoint(RenderSize.Width - x, RenderSize.Height - lineLength)));

pathGeometry.AddGeometry(newLineGeometry(newPoint(RenderSize.Width, RenderSize.Height - x),newPoint(RenderSize.Width - lineLength - x, RenderSize.Height - x)));

drawingContext.DrawGeometry(Brushes.Transparent, selectedBorderPen, pathGeometry);

}

base.OnRender(drawingContext);

}

#endregion

}

}

Code Snippet

// AdornerPanel.cs

using System;

using System.Collections.Generic;

using System.Text;

using System.Windows.Controls;

using System.Windows.Controls.Primitives;

using System.Windows.Media;

using System.Windows;

using System.Diagnostics;

using System.Windows.Documents;

namespace AdornerRendering

{

publicclassAdornerPanel :Panel

{

#region Consts

publicstaticreadonlySize Layout1x1 =newSize(1, 1);

publicstaticreadonlySize Layout4x4 =newSize(4, 4);

publicstaticreadonlySize Layout8x8 =newSize(8, 8);

publicstaticreadonlySize Layout16x16 =newSize(16, 16);

publicstaticreadonlySize Layout32x32 =newSize(32, 32);

#endregion

#region Dependency Properties

#region Layout Property

///Identifies the Layout dependency property

publicstaticreadonlyDependencyProperty LayoutProperty =

DependencyProperty.Register("Layout",typeof(Size),typeof(AdornerPanel),

newFrameworkPropertyMetadata(Layout1x1,

FrameworkPropertyMetadataOptions.AffectsRender |

FrameworkPropertyMetadataOptions.AffectsArrange |

FrameworkPropertyMetadataOptions.AffectsMeasure, OnLayoutChanged));

staticvoid OnLayoutChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)

{

AdornerPanel panel = dasAdornerPanel;

}

publicSize Layout

{

get {return (Size)GetValue(LayoutProperty); }

set { SetValue(LayoutProperty,value); }

}

#endregion

#endregion

#region Overrides

protectedoverrideSize MeasureOverride(Size availableSize)

{

Size childSize =newSize(

availableSize.Width / Layout.Width,

availableSize.Height / Layout.Height);

foreach (UIElement childin InternalChildren)

{

child.Measure(childSize);

}

return availableSize;

}

protectedoverrideSize ArrangeOverride(Size finalSize)

{

int childNumber = 0;

Size childSize =newSize(

finalSize.Width / Layout.Width,

finalSize.Height / Layout.Height);

foreach (UIElement childin InternalChildren)

{

Point childPosition =newPoint(

childSize.Width * (childNumber % (int)Layout.Width),

childSize.Height * (childNumber / (int)Layout.Width));

child.Arrange(newRect(childPosition, childSize));

++childNumber;

}

return finalSize;

}

protectedoverridevoid OnVisualChildrenChanged(DependencyObject visualAdded,DependencyObject visualRemoved)

{

UIElement element = visualAddedasUIElement;

if (visualAdded !=null)

{

AdornerLayer layer =AdornerLayer.GetAdornerLayer(element);

#region Assert

Debug.Assert(layer !=null,string.Format("Make sure that {0} is decorated with an AdornerDecorator", visualAdded));

#endregion

layer.Add(newSelectionAdorner(element));

}

base.OnVisualChildrenChanged(visualAdded, visualRemoved);

}

#endregion

}

}

Code Snippet

<Windowx:Class="AdornerRendering.Window1"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:local="clr-namespace:AdornerRendering"

Title="AdornerRendering"Height="380"Width="300">

<Grid>

<Grid.RowDefinitions>

<RowDefinitionHeight="80"/>

<RowDefinition />

</Grid.RowDefinitions>

<WrapPanel>

<ButtonContent="Add"Click="buttonAdd_Click" />

<TextBoxx:Name="textBoxCount"Text="1"Width="50" />

<ButtonContent="Remove Selected"Click="buttonRemove_Click" />

<ButtonContent="1x1"Click="buttonLayout_Click"

Tag="{x:Static local:AdornerPanel.Layout1x1}"/>

<ButtonContent="4x4"Click="buttonLayout_Click"

Tag="{x:Static local:AdornerPanel.Layout4x4}"/>

<ButtonContent="8x8"Click="buttonLayout_Click"

Tag="{x:Static local:AdornerPanel.Layout8x8}"/>

<ButtonContent="16x16"Click="buttonLayout_Click"

Tag="{x:Static local:AdornerPanel.Layout16x16}"/>

</< FONT><WrapPanel>

<AdornerDecoratorGrid.Row="1">

<local:AdornerPanelx:Name="_panel" />

</< FONT></AdornerDecorator>

</< FONT></Grid>

</< FONT></Window>

Code Snippet

// Window1.xaml.cs

using System;

using System.Collections.Generic;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Shapes;

namespace AdornerRendering

{

publicpartialclassWindow1 : System.Windows.Window

{

public Window1()

{

InitializeComponent();

}

void buttonAdd_Click(object sender,RoutedEventArgs e)

{

int count =int.Parse(textBoxCount.Text);

for (int i = 0; i < count; i++)

{

TextBlock textBlock =newTextBlock(newRun("Item " + (i + 1).ToString()));

_panel.Children.Add(textBlock);

}

}

void buttonRemove_Click(object sender,RoutedEventArgs e)

{

}

void buttonLayout_Click(object sender,RoutedEventArgs e)

{

Button button = senderasButton;

_panel.Layout = (Size)button.Tag;

}

}

}

Code Snippet

<Applicationx:Class="AdornerRendering.App"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

StartupUri="Window1.xaml">

</Application>

Code Snippet

// App.xaml.cs

using System;

using System.Windows;

using System.Data;

using System.Xml;

using System.Configuration;

namespace AdornerRendering

{

///<summary>

/// Interaction logic for App.xaml

///</summary>

publicpartialclassApp : System.Windows.Application

{

}

}

[30467 byte] By [deepforest] at [2008-1-9]
# 1

It’s a bug in the AdornerLayer.

What’s happening is that the layout system has a threshold beyond which it will not add new UIElements to the invalidated queue. Instead, it invalidates parents up to the root and adds the root element instead. Unfortunately the AdornerLayer is invalidating child Adorners inside its own Arrange, so even though the AdornerLayer is flagged dirty while invalidating child Adorners past 144, it (incorrectly) sets itself clean exiting Arrange. Later, when the root element (Window1 in this case) is Arranged, it skips the “clean” AdornerLayer and all the pending Adorners.

The best work around I can think of is to use fewer Adorners. You should be able to use a single Adorner with internal logic to cover any number of elements.

Ben

BenWestbrook-MSFT at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Presentation Foundation (WPF)...
# 2

Hi Ben, thanks for your quick answer.

Our company spent a month for developing a solution based on this model. As we don't want to spend another month for developing an alternative solution, we will be very appreciating for providing a patch for this BUG. Our solution is highly depends on it.

Thanks a lot for your effort

deepforest at 2007-10-3 > top of Msdn Tech,Visual Studio Orcas,Windows Presentation Foundation (WPF)...

Visual Studio Orcas

Site Classified