WPF: Drawing on top of a TextBlock

I want to be able to draw on to the top of a TextBlock, and have found a way to do this, but i cannot remove the drawing once it is there. Here is the code.

   public class DerivedTextBlock : TextBlock {

      public Boolean DrawExtra {
         get { return (Boolean)GetValue(DrawExtraProperty); }
         set { SetValue(DrawExtraProperty, value); }
      }

      // Using a DependencyProperty as the backing store for DrawExtra.  This enables animation, styling, binding, etc...
      public static readonly DependencyProperty DrawExtraProperty =
          DependencyProperty.Register("DrawExtra", typeof(Boolean), typeof(DerivedTextBlock), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsArrange));

      public DrawingVisual DrawingVisual { get; set; }

      public DerivedTextBlock() {
         DrawingVisual = this.CreateDrawingVisualRectangle();
      }

      protected override int VisualChildrenCount {
         get {
            //if we want to draw our extra info, add one to
            // our visualChildrenCount, usually with a textblock it is 0
            if (DrawExtra) {
               return base.VisualChildrenCount + 1;
            }
            else {
               return base.VisualChildrenCount;
            }
         }
      }

      protected override Visual GetVisualChild(int index) {
         return DrawingVisual;
      }

      // Create a DrawingVisual that contains a rectangle.
      private DrawingVisual CreateDrawingVisualRectangle() {
         DrawingVisual drawingVisual = new DrawingVisual();

         // Retrieve the DrawingContext in order to create new drawing content.
         DrawingContext drawingContext = drawingVisual.RenderOpen();

         // Create a rectangle and draw it in the DrawingContext.
         Rect rect = new Rect(new Point(10.0, 0), new Size(10.0 / 2.0, 10));
         drawingContext.DrawRectangle(Brushes.LightBlue, (Pen)null, rect);

         // Persist the drawing content.
         drawingContext.Close();

         return drawingVisual;
      }
   }

Reason I want to do this: We have a datagrid with a lot of cells, each cell displaying text. we show some validation information on the cells and we do this by using a template with a textblock and some paths hosten in a grid. the overhead of this adds extra elements to the visual tree and when we have to redraw (on loading, switching windows or on a sort) it takes a lot longer the more elements in the visual tree. when it is just a textblock it is about 1/3 - 1/2 faster than having the control with a grid. So we would like to draw our validation stuff right on top of the textbox.

Answers


Your problems are:

  1. GetVisualChild() should return base.GetVisualChild(index) except when index==base.VisualChildrenCount.
  2. You forgot to call AddVisualChild() when DrawingExtra becomes true or DrawingVisual changes
  3. You forgot to call RemoveVisualChild() when DrawingExtra becomes false or DrawingVisual changes

You can fix #2 and #3 by setting a PropertyChangedCallback on DrawingExtra and adding code to the setter of DrawingVisual.

Explanation: It is the AddVisualChild() call that actually adds the visual to the tree. What is happening is that your visual is being found and displayed "accidentally" because of your error in GetVisualChild(), but it is not being properly linked into the visual tree so you'll encounter many problems.

Update

I edited your code as described above, and it worked perfectly. Here are the changes:

...
      {
        PropertyChangedCallback = (obj, e) =>
          {
            var textBlock = (DerivedTextBlock)obj;
            if((bool)e.OldValue) textBlock.RemoveVisualChild(textBlock.DrawingVisual);
            if((bool)e.NewValue) textBlock.AddVisualChild(textBlock.DrawingVisual);
          }
      });

  public DrawingVisual DrawingVisual
  {
    get { return _drawingVisual; }
    set
    {
      if(DrawExtra) RemoveVisualChild(_drawingVisual);
      _drawingVisual = value;
      if(DrawExtra) AddVisualChild(_drawingVisual);
    }
  }
  private DrawingVisual _drawingVisual;

...

  protected override int VisualChildrenCount
  {
    get { return base.VisualChildrenCount + (DrawExtra ? 1 : 0); }
  }

  protected override Visual GetVisualChild(int index)
  {
    return index==base.VisualChildrenCount ? DrawingVisual : base.GetVisualChild(index);
  }

Need Your Help

How to include html files like include in php?

php html file header include

How can we include files inside html, like how we usually include files in Php.