在单独的控件中绘制 ListBox 项的行号

Draw line number of ListBox items in a separated control

我想在 ListBox 的左侧绘制行号,非常类似于 AvalonEdit does with LineNumberMargin. When ShowLineNumbers is true, it creates LineNumberMargin like this

无论如何,我看了一下他们是如何做到的,并理解了,现在我正在尝试应用类似的东西,但使用 ListBox(及其项目)作为绘图的来源。

我的控件是这样工作的:我有一个单独的 ItemsControl 停靠在 ListBox 的左侧。每个 ItemsControl 的项目都是一个 UIElement。 DesignerLineNumberMargin 是 ItemsControl 的项目之一,当设置了 ListBox 的 ItemsSource 时,我将 ListBox 附加到 DesignerLineNumberMargin。呈现 DesignerLineNumberMargin 时,我遍历 ListBox 的项目并绘制行号。

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();
    }
}

我的问题是找出 y 坐标。调用OnRender时我不知道ListBoxItemHeight:Height、ActualHeight、DesiredSize始终为0。

有什么见解吗?

我想那是因为你的 MeasureOverride() returns 一个 Size 只有一个 Width,但是 Height 设置为零。

尝试将 return 语句更改为:

 return new Size(text.Width, text.Height);