0

I want to draw line numbers in the left of a ListBox, very similar to what AvalonEdit does with LineNumberMargin. When ShowLineNumbers is true, it creates LineNumberMargin like this.

Anyways, I took a look at how they do it, and understood, and now I'm trying to apply something similar but using a ListBox (and its items) as a source for the drawing.

My control works like this: I have a separated ItemsControl docked to the left of ListBox. Each ItemsControl's item is an UIElement. DesignerLineNumberMargin is one of the ItemsControl's item and when the ItemsSource of the ListBox is set, I attach the ListBox to the DesignerLineNumberMargin. When DesignerLineNumberMargin is rendered, I iterate over ListBox's items and draw the line numbers.

DesignerLineNumberMargin.cs

public interface IMetadataAware
{
    void Attach(ItemsControl control);
    void Detach(ItemsControl control);
}

public class DesignerLineNumberMargin : FrameworkElement, IMetadataAware
{
    private ItemsControl control;
    private Typeface typeface;
    private double emSize;
    private int maxLineNumberLength = 2;

    static DesignerLineNumberMargin()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(DesignerLineNumberMargin),
            new FrameworkPropertyMetadata(typeof(DesignerLineNumberMargin)));
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        typeface = CreateTypeface();
        emSize = (double)GetValue(TextBlock.FontSizeProperty);

        var text = CreateText(new string('9', maxLineNumberLength));

        return new Size(text.Width, 0);
    }

    private FormattedText CreateText(string text)
    {
        return
            new FormattedText(
            text,
            CultureInfo.CurrentCulture,
            FlowDirection.LeftToRight,
            typeface,
            emSize,
            (Brush)GetValue(Control.ForegroundProperty));
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        if (control == null)
            return;

        var renderSize = RenderSize;
        var foreground = (Brush)GetValue(Control.ForegroundProperty);

        for (int index = 0; index < control.Items.Count; index++)
        {
            var item = control.Items[index];
            var container = (FrameworkElement)control.ItemContainerGenerator.ContainerFromItem(item);

            var text = CreateText((index + 1).ToString(CultureInfo.CurrentCulture));
            //var y = container.Height;
            var y = RenderSize.Height / (double)control.Items.Count;

            drawingContext.DrawText(text, new Point(renderSize.Width - text.Width, y + index));
        }
    }

    private Typeface CreateTypeface()
    {
        var element = this;
        return new Typeface(
            (FontFamily)element.GetValue(TextBlock.FontFamilyProperty),
            (FontStyle)element.GetValue(TextBlock.FontStyleProperty),
            (FontWeight)element.GetValue(TextBlock.FontWeightProperty),
            (FontStretch)element.GetValue(TextBlock.FontStretchProperty));
    }

    public void Attach(ItemsControl control)
    {
        this.control = control;

        var descriptor = TypeDescriptor.GetProperties(control)["ItemsSource"];
        descriptor.AddValueChanged(control, OnItemsSourceChanged);
    }

    private void OnItemsSourceChanged(object sender, EventArgs e)
    {
        if (this.control.ItemsSource is INotifyCollectionChanged)
            (this.control.ItemsSource as INotifyCollectionChanged).CollectionChanged += CollectionChanged;
    }

    private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        InvalidateVisual();
    }

    public void Detach(ItemsControl control)
    {
        if (this.control == control)
        {
            var descriptor = TypeDescriptor.GetProperties(control)["ItemsSource"];
            descriptor.RemoveValueChanged(control, OnItemsSourceChanged);

            if (this.control.ItemsSource is INotifyCollectionChanged)
                (this.control.ItemsSource as INotifyCollectionChanged).CollectionChanged -= CollectionChanged;
            this.control = null;
        }

        InvalidateVisual();
    }
}

The problem for me is figuring out the y-coordinate. When OnRender is called I do not know the Height of the ListBoxItem: Height, ActualHeight, DesiredSize is always 0.

enter image description here

Any insights?

JobaDiniz
  • 862
  • 1
  • 14
  • 32

1 Answers1

1

I guess that's because your MeasureOverride() returns a Size with only a Width, but Height set to zero.

Try change the return statement to:

 return new Size(text.Width, text.Height);
René Vogt
  • 43,056
  • 14
  • 77
  • 99