Intra-Text Adornments Replacing Collapsed Region Exception When Line Starts With Tab

SyntaxEditor for WPF Forum

Posted 5 months ago by Will Gauthier
Version: 21.1.3
Platform: .NET 4.6
Environment: Windows 10 (64-bit)
Avatar

I am encountering an

ArgumentException: Specified Visual is already a child of another Visual or the root of a CompositionTarget.

when rendering an intra-text adornment in place of a collapsed region when the collapsed text is preceded by a tab character, but not if the collapsed text starts the line.

The feature I'm implementing replaces string matches with visual adornments. A menu command allows the user to select among the source string options, and their choice is inserted via IEditorDocument.ReplaceText where the TextRange is the current view's Selection.GetTextRanges. On a callback registered with the SyntaxEditor's DocumentTextChanged event, I remove all tags that intersect with the changed snapshot range before generating a new tag for each match.

My tagging setup closely adheres to the examples and tips in the documentation section. I have a CollapsedRegionTag that implements ICollapsedRegionTag and IIntraTextSpacerTag, a CollapsedRegionTagger that extends CollectionTagger<ICollapsedRegionTag> and implements ITagger<IIntraTextSpacerTag>, and a CollapsedRegionAdornmentManager that extends IntraTextAdornmentManagerBase<IEditorView, CollapsedRegionTag>.

Comments (7)

Posted 5 months ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Will,

That exception message would indicate the visual you are using for your adornment is already within the visual tree and wasn't yet removed before trying to use it elsewhere.  It's hard to say what is causing that without a sample to debug.

My guess is this is due to a delay between the tagging changes and when the adornments are updating.  For instance, a layout invalidation occurs when tags are changed.  That invalidation requests that a new layout happen but the actual layout doesn't necessarily happen synchronously, for performance reasons.  Therefore if you remove the tags, the related adornments will likely still be there briefly until the next layout run occurs.  If you add tags back in right after removing the tags (but before the layout run occurs) and are trying to reuse those same adornments, I could see the exception you mentioned occurring.

It would be better to not reuse an adornment visual unless you are positive it is not in the visual tree.  The easy way would be to just not reuse adornment visuals at all.  But if you feel it helps to reuse them for performance reasons, you could keep a pool of "removed" adornment visuals, and only reuse adornment visuals from that pool that have been removed from the visual tree at that point.


Actipro Software Support

Posted 5 months ago by Will Gauthier
Avatar

I'm not certain if the built-in IntraTextAdornmentManagerBase I'm extending is pooling the adornments, but otherwise there shouldn't be any reuse of objects.

After calling RemoveAll(x => x.VersionRange.Translate(textSnapshot).IntersectsWith(changedSnapshotRange)) on my CollapsedRegionTagger at the start of the DocumentTextChanged callback, all the tags I add to the collection are newly instantiated, along with their UIElement Content and text ranges. If it matters, I'm using the tag itself as its own Key. I also tried setting the Key as new object() and Guid.NewGuid(), but it didn't make a difference.

This is my function for creating the UIElements that the adornments draw:

private static UIElement GetUIElement(object obj)
{
  Grid grid = new Grid()
  {
    Background = new SolidColorBrush(Color.FromRgb(63, 63, 63)),
  };

  ContentPresenter contentPresenter = new ContentPresenter()
  {
    Content = obj,
  };
  grid.Children.Add(contentPresenter);

  return grid;
}

This is my override for the AddAdornment method:

protected override void AddAdornment(AdornmentChangeReason reason, ITextViewLine viewLine, TagSnapshotRange<CollapsedRegionTag> tagRange, TextBounds bounds)
{
  Point location = new Point(Math.Round(bounds.Left), bounds.Top);

  AdornmentLayer.AddAdornment(reason, tagRange.Tag.Content, location, tagRange.Tag.Key, removedCallback: null);
}

The crux of the problem seems to be that a layout update happens twice, but only if the text of the line is \t<match string>, not if it's just <match string>. The callstack ending in CollapsedRegionAdornmentManager.AddAdornment is all external code, but on both the first and problem second add it looks roughly like: System.Windows.ContextLayoutManager.UpdateLayout() -> ActiproSoftware.Windows.Controls.Rendering.CanvasControl.RaiseDrawEvent -> ActiproSoftware.Windows.Controls.SyntaxEditor.Primitives.TextView.OnCanvasDraw -> ActiproSoftware.Windows.Controls.SyntaxEditor.Implementation.TextViewCanvasRenderer.Render -> ActiproSoftware.Windows.Controls.SyntaxEditor.Implementation.TextViewLineManager.PerformLayout -> ActiproSoftware.Windows.Controls.SyntaxEditor.Adornments.Implementation.IntraTextAdornmentManagerBase<ActiproSoftware.Windows.Controls.SyntaxEditor.IEditorView, CollapsedRegionTag>.OnViewTextAreaLayout

Posted 5 months ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Will,

It's tough to say what's happening without debugging this particular scenario.  If you'd like us to look, please make a new simple sample project that shows it happening and send that to our support address, mentioning this thread.  Be sure to exclude the bin/obj folders from the .zip you send so it doesn't get spam blocked.  Then we can debug with that and see what's going on.  Thanks!


Actipro Software Support

Posted 5 months ago by Will Gauthier
Avatar

I'm putting together a sample project showcasing the issue for you to look at, but in the meantime I've discovered some additional info that might give insight into what's wrong. My implementation of the ITagger<IIntraTextSpacerTag> interface's GetTags method is hit three times after I insert the string that should be collapsed and adorned into the document. The first call happens before I add the tag, and it is passed the TextSnapshotRange "\tCARD_ID(7)", which is the whole line since CARD_ID(7) is the text I inserted. The second call is passed the TextSnapshotRange's "\t" and "", the first and last characters in the document. But then, strangely, on the third call the tagger is asked for the tags for the TextSnapshotRange "C", the first character in the inserted string. Is this expected behavior? It seems wrong to me that an aggregator for the IIntraTextSpacerTag would be interested in a seemingly already collapsed region. Have you seen anything like this before?

Posted 5 months ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Will,

When it's building up the list of ranges to examine for intra-test spacers, it does have a step where it examines the intra-text spacers that cross the first character of a collapsed region.  This a performance optimization so that we can find an intra-text spacer tag with the same range as the collapsed region but not get all other intra-text spacer tags within the collapsed region.  The reason we do this to start with is that if there is an intra-text spacer tag that matches the same range as the collapsed region, we will show it.


Actipro Software Support

Posted 5 months ago by Will Gauthier
Avatar

That makes sense; thanks for the explanation.

I've followed your suggestion and emailed over a simple sample project demonstrating the issue. Hopefully you will be able to debug the problem.

Posted 5 months ago by Actipro Software Support - Cleveland, OH, USA
Avatar

Hi Will,

We've been looking but don't see the email you mentioned.  Be sure to exclude the bin/obj folders from the .zip you send so it doesn't get spam blocked by a mail server along the way, since doesn't appear to have reached our ticket system if you did send it.


Actipro Software Support

The latest build of this product (v22.1.2) was released 2 months ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.