0

I need to display a block of text in a resizable column. The text should wrap with overflow but, for a given column size, the user should be able to scroll horizontally to view the overflown text.

I do not believe this can be achieved w/ out of the box controls; if I'm wrong about that please tell me. I have attempted to achieve this with a custom control:

public class Sizer : ContentPresenter
{
    static Sizer()
    {
        ContentPresenter.ContentProperty.OverrideMetadata(typeof(Sizer), new FrameworkPropertyMetadata(ContentChanged)); ;
    }

    public Sizer() : base() {}        

    protected override Size MeasureOverride(Size constraint)
    {
        var childWidth = Content==null ? 0.0 : ((FrameworkElement)Content).RenderSize.Width;
        var newWidth = Math.Max(RenderSize.Width, childWidth);
        return base.MeasureOverride(new Size(newWidth, constraint.Height));
    }

    private static void ContentChanged(DependencyObject dep, DependencyPropertyChangedEventArgs args)
    {
        var @this = dep as Sizer;
        var newV = args.NewValue as FrameworkElement;
        var oldV = args.OldValue as FrameworkElement;
        if (oldV != null)
            oldV.SizeChanged -= @this.childSizeChanged;
        if(newV!=null)
            newV.SizeChanged += @this.childSizeChanged;
    }

    private void childSizeChanged(object sender, SizeChangedEventArgs e)
    {
        this.InvalidateMeasure();
    }
}

...and I can test it in a simple WPF application like so:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="200"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto" VerticalAlignment="Top">
        <local:Sizer>
            <local:Sizer.Content>
                <TextBlock Background="Coral" Text="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa  aaaaaaaaaaaaaaaaaaa" 
                           TextWrapping="WrapWithOverflow" />
            </local:Sizer.Content>
        </local:Sizer>
    </ScrollViewer>
    <GridSplitter Grid.Column="1" Background="Black" VerticalAlignment="Stretch" HorizontalAlignment="Left" Width="5" />
</Grid>

This works, after a fashion. It wraps and displays the text correctly, and the horizontal scrollbar lets me view the overflown text. If I resize the column to the right (larger) the text re-wraps correctly. However, if I resize the column to the left (smaller) the text will not re-wrap. So, for instance, if I resize the column to the right so that all the text is on one line it will remain all on one line regardless of any subsequent re-sizing. This is an unacceptable bug.

I have tinkered w/ this code a great deal although I haven't had what you'd a call a good strategy for finding a solution. I do not know and have not been able to discover any mechanism for forcing a textblock to re-wrap its contents. Any advice?

mcwyrm
  • 1,571
  • 1
  • 15
  • 27
  • 1
    Only a comment but I have seen this where the UI rendering just does not re-measure when you reduce the size. And even found something from MSFT that was by design. – paparazzo Oct 03 '14 at 15:49
  • That seems promising. Any chance you've got a link to what you read about it? – mcwyrm Oct 03 '14 at 16:34
  • No, but if you search my questions I know I asked about it on SO. And I got a great answer that only told me it was a dead end. – paparazzo Oct 03 '14 at 16:46
  • I suspect you are referring to this: "[VirtulizingStackPanel]’s Measure algorithm ... remembers the largest size ever discovered and forces all future Measure calls to report a size at least as large." There's no VSP in my tree, but if ScrollContentPresenter does something similar it would produce the behavior I'm seeing. Thanks. http://stackoverflow.com/a/3090616/2970316 – mcwyrm Oct 03 '14 at 17:42
  • Yes that was it but I felt like I ran into in other areas. Hey it was only a comment. – paparazzo Oct 03 '14 at 17:44
  • Can't possibly be a dead end. Placing a wrap panel or WrapWithOverflow text block in a window and resizing yields the correct behavior. Ditto putting one in a GridView. There *is* a way of making these controls re-wrap when the available size decreases. – mcwyrm Oct 06 '14 at 13:41
  • Sorry I tried to help. For me it was a dead end. – paparazzo Oct 06 '14 at 16:55

1 Answers1

0

I was able to get this working (w/ some limitations) by adding the following to the custom control:

    //stores first ScrollViewer ancestor
    private ScrollViewer parentSV = null;

    //stores collection of observed valid widths
    private SortedSet<double> wrapPoints = new SortedSet<double>();

    protected override void OnVisualParentChanged(DependencyObject oldParent)
    {
        parentSV = this.FindParent<ScrollViewer>();
        base.OnVisualParentChanged(oldParent);
    }

... and editing MeasureOverride list this:

protected override Size MeasureOverride(Size constraint)
    {
        if (parentSV != null)
        {
            var childWidth = Content == null ? 0.0 : ((FrameworkElement)Content).RenderSize.Width;
            var viewportWidth = parentSV.ViewportWidth;
            if (childWidth > viewportWidth + 5)
                wrapPoints.Add(childWidth);
            var pt = wrapPoints.FirstOrDefault(d => d > viewportWidth);
            if (pt < childWidth)
                childWidth = pt;

            return base.MeasureOverride(new Size(childWidth, constraint.Height));
         }
         else
             return base.MeasureOverride(constraint);
    }

I do not like this at all. It doesn't work for WrapPanels (although that isn't a required use case for me), it flickers occasionally and I'm concerned that the wrapPoints collection may grow very large. But it does what I need it to do.

mcwyrm
  • 1,571
  • 1
  • 15
  • 27