WPF DataGrid and Adorners

1.7k views Asked by At

I am using an Adorner to place an indicator triangle in selected cells of a WPF DataGrid (the same effect as you get when inserting a comment in a cell in Excel). Unfortunately I am getting random Adorners appearing where they shouldn't.

Supposing there are 3 cells that should have the Adorner; I am getting 3 extra cells that also have the Adorner. I have proved that only 3 are being created in code even though there are 6 appearing. I create/remove the Adorners in the ElementGenerated event.

The extra instances are always on cells that are not yet in the visual area of the grid and so I am fairly certain that the problem is due to virtualization of the grid columns and that the grid is re-using cells rather than creating new ones and as such the ElementGenerated event does not fire again and the Adorner is not removed where it is not needed.

I can't find an event that I could use when the Cell is re-used. Any suggestions would be gratefully received.

This is the code for the triangle Adorner:-

public class TriangleAdorner : Adorner
{
    private readonly double _offsetX;
    private readonly double _offsetY;

    public TriangleAdorner(UIElement adornedElement)
        : this(adornedElement, 0, 0)
    {
    }

    public TriangleAdorner(UIElement adornedElement, double offsetX, double offsetY)
        : base(adornedElement)
    {
        _offsetX = offsetX;
        _offsetY = offsetY;
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        //Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);

        // all examples seem to use the above but this didn't get the adorner in the correct place for me
        Rect adornedElementRect = new Rect(this.AdornedElement.RenderSize);

        PointCollection myPointCollection = new PointCollection
                                                {
                                                    new Point(adornedElementRect.TopLeft.X + 6 + _offsetX, adornedElementRect.TopLeft.Y + 1 + _offsetY), 
                                                    new Point(adornedElementRect.TopLeft.X + 1 + _offsetX, adornedElementRect.TopLeft.Y + 1 + _offsetY), 
                                                    new Point(adornedElementRect.TopLeft.X + 1 + _offsetX, adornedElementRect.TopLeft.Y + 6 + _offsetY)
                                                };

        StreamGeometry streamGeometry = new StreamGeometry();
        using (StreamGeometryContext geometryContext = streamGeometry.Open())
        {
            geometryContext.BeginFigure(myPointCollection[0], true, true);
            PointCollection points = new PointCollection
                                         {
                                             myPointCollection[1],
                                             myPointCollection[2]
                                         };
            geometryContext.PolyLineTo(points, true, true);
        }

        drawingContext.DrawGeometry(Brushes.Red, new Pen(Brushes.Red, 0), streamGeometry);

    }

    protected override Size MeasureOverride(Size constraint)
    {
        var result = base.MeasureOverride(constraint);

        InvalidateVisual();
        return result;
    }

And the event that adds/removes:-

private void DataGrid_ElementGenerated(object sender, ElementGeneratedEventArgs e)
{
        FrameworkElement element = (FrameworkElement) e.DataGridCell;
        if (element == null) return;

        AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(element);
        if (adornerLayer != null)
        {

            if (CellAdornerRequired())
            {
                Adorner[] children = adornerLayer.GetAdorners(element);
                if (children == null || !children.Any())
                {
                    adornerLayer.Add(new TriangleAdorner(element));
                }
            }
            else
            {
                Adorner[] children = adornerLayer.GetAdorners(element);
                if (children != null && children.Any())
                {
                    foreach (var adorner in children)
                    {
                        adornerLayer.Remove(adorner);
                    }
                }
            }
        }
    }
1

There are 1 answers

1
Joost van den Boom On

You can use the DataGrid.LoadingRow and DataGrid.UnloadingRow events to get notified when a row is being re-used by virtualization. Unfortunately I don't think you can get to the cells from there.

You could try to attach to the DataGridCell.Loaded and DataGridCell.DataContextChanged events to create and update your adorners.