Search code examples
c#xamluwpmarquee

Textblock margin causes out of bounds text


I'm currently trying to create a visual component to have scrolling text (left to right and right to left) - pretty much an html marquee.

I have a grid divided in several columns & rows, and I want to place my component inside one of the grid slots.

The grid (named UIGrid) is generated like this :

for (int i = 0; i < xDivisions; i++)
{
    ColumnDefinition newColumn = new ColumnDefinition();
    UIGrid.ColumnDefinitions.Add(newColumn);
}
for (int i = 0; i < yDivisions; i++)
{
    RowDefinition newRow = new RowDefinition();
    UIGrid.RowDefinitions.Add(newRow);
}

The component I'm adding is just a border with a textblock as a child. I place the border inside the Grid like this :

border = new Border();
Grid.SetColumn(border, xPosition);
Grid.SetRow(border, yPosition);

textBlock = new TextBlock();
border.Child = textBlock;
textBlock.Text = "Scrolling text from left to right";

UIGrid.Children.Add(border);

I'm using a timer to increment the textblock margin, here's the timer callback simplified body :

textBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
double textWidth = textBlock.DesiredSize.Width;
double visibleWidth = componentBase.ActualWidth;
double targetMargin = textWidth < visibleWidth ? visibleWidth : textWidth;

if (margin.Left == targetMargin)
{
    margin.Left = -textWidth;
} else
{
    margin.Left++;
}

When the text slides from left to right, it behaves nicely : https://s10.postimg.org/p0nt7vl09/text_good.png Text "leaving" the grid slot is hidden.

However, when I set the textblock's margin as negative so it may come back inside the viewable area from the left, the text is visible even though it's outside its allocated slot : https://s10.postimg.org/pownqtjq1/text_bad.png

I've tried using padding instead, but I can't set a negative padding. I've tried a few other things, but I feel like I've encountered a roadblock.

What could I do to get a nicely scrolling text ?


Solution

  • Ed Plunkett led me in the right direction with the Clip property. The idea is to do this :

    border.Clip = new RectangleGeometry
    {
        Rect = new Rect(0, 0, border.ActualWidth, border.ActualHeight)
    };
    

    Of course, that doesn't work if the border hasn't been rendered yet (and of course it isn't when my code is running). You can force the measurement to take place using 'Measure' as I did to measure the text length in pixels, but it behaved strangely on my border. I wouldn't get the correct size at all.

    In the end, I simply subscribed to the border's SizeChanged event :

    border.SizeChanged += OnSizeComputed;
    

    When that event is fired, I create the RectangleGeometry using ActualWidth & ActualHeight.