Paul Michaels
Paul Michaels

Reputation: 16685

WPF Custom Control inside ItemsControl

I have a custom control, which looks broadly like this:

public class MyTextBlockTest : Control
{
    static MyTextBlockTest()
    {
    }

    public static readonly DependencyProperty TextProperty =
         DependencyProperty.Register("Text", typeof(string),
         typeof(MyTextBlockTest), new FrameworkPropertyMetadata(""));

    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        base.OnRender(drawingContext);

        FontFamily font = new FontFamily("Times New Roman");
        Typeface typeface = new Typeface(font, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
        FormattedText ft = new FormattedText(Text,
                                             System.Globalization.CultureInfo.CurrentCulture,
                                             System.Windows.FlowDirection.LeftToRight,
                                             typeface,
                                             15.0,
                                             Brushes.Black);

        var point = this.PointToScreen(new Point(0, 0));
        drawingContext.DrawText(ft, point);            
    }
}

I'm trying to use this control inside an ItemsControl:

<ScrollViewer>
    <ItemsControl ItemsSource="{Binding BigList, ElementName=MainWindowView}" Margin="0,-1,0,1">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <!--<TextBlock Text="{Binding}"/>-->
                <controls:MyTextBlockTest Text="{Binding}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</ScrollViewer>

If I enable the TextBlock that's commented out, the list displays fine; however, with my custom control, WPF isn't placing the text in the right place; more specifically, it seems to be simply printing all elements on top of each other.

The this.PointToScreen call was my initial idea to give it a prompt, but I'm unsure (even if this worked, which it doesn't) whether this would react favourably with the ScrollViewer.

I realise I could simply base this control on the TextBlock control, but I specifically want to base it off a lower level control as a performance experiment. Please could someone point me toward a method to place the Custom Control on the screen correctly?

Upvotes: 1

Views: 1598

Answers (1)

Clemens
Clemens

Reputation: 128013

Your minimal custom TextBlock should at least also override the MeasureOverride method to return its size. Otherwise the control has zero width and height, and all items in the ItemsControl are drawn on top of each other.

The Text property should therefore be registered with the FrameworkPropertyMetadataOptions.AffectsMeasure flag.

public class MyTextBlock : FrameworkElement
{
    private FormattedText formattedText;

    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register(
            "Text", typeof(string), typeof(MyTextBlock),
            new FrameworkPropertyMetadata(
                string.Empty,
                FrameworkPropertyMetadataOptions.AffectsMeasure,
                (o, e) => ((MyTextBlock)o).TextPropertyChanged((string)e.NewValue)));

    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    private void TextPropertyChanged(string text)
    {
        var typeface = new Typeface(
            new FontFamily("Times New Roman"),
            FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);

        formattedText = new FormattedText(
            text, CultureInfo.CurrentCulture,
            FlowDirection.LeftToRight, typeface, 15, Brushes.Black);
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        return formattedText != null
            ? new Size(formattedText.Width, formattedText.Height)
            : new Size();
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        if (formattedText != null)
        {
            drawingContext.DrawText(formattedText, new Point());
        }
    }
}

Upvotes: 4

Related Questions