Замена отсутствующей опции TextTrimming "CharacterEllipsis" в Silverlight

Silverlight (по крайней мере, начиная с версии 4) не имеет CharacterEllipsis вариант для TextTrimming, который имеет WPF. Это может быть использовано на TextBlock, Это означает, что если недостаточно места для отображения "Это невероятно", я мог бы обрезать до "Это...", но не до "Это невероятно...", что мы бы предпочли.

Хотя я бы попробовал реализовать нашу собственную функцию обрезки текста. В принципе, это не так сложно. Довольно глупый способ - измерить пиксели для строки, сравнить с доступной шириной и манипулировать строкой, обрезая последний символ и добавляя "..." в цикле, пока текст по-прежнему не помещается. Вот пример того, как это может работать:

// Not perfect but good enough for us
private bool AutoTrim(string fullText, TextBlock textBlock, double maxWidth)
{
    double factor = maxWidth / textBlock.ActualWidth;
    if (factor > 1)
        return false;

    int newTextLength = (int)Math.Floor((double)fullText.Length * factor);
    string trimTest;
    do
    {
        trimTest = fullText.Substring(0, newTextLength--);
        textBlock.Text = trimTest + "..."; // problematic...
        factor = maxWidth / textBlock.ActualWidth;
    }
    while (factor < 1 && newTextLength > 0);

    return true;
}

Но делать это в коде позади (или в пределах Behavior) приводит к некоторым проблемам: например, когда мы хотим обновить отображаемый текст и установить текстовые блоки TextBlock1.Text = ... Свойство, это на самом деле может изменить нашу viewModel, если текст привязан к свойству ViewModel. Другие проблемы возникают, когда мы заметили, что view и viewModel могут работать по какой-то причине (мы заметили это в ListBox).

У вас есть лучшее представление о том, как решить эту проблему хорошим способом?

4 ответа

DynamicTextBox Робби Ингебретсена делает это, оборачивая TextBlock в пользовательский элемент управления и измеряя доступный размер. Он соответствует режиму обрезки текста CharacterEllipsis в WPF. Режим WordEllipsis был добавлен в Windows Phone 7 Mango, но здесь это не сильно помогает.

Дэн Уолин использовал конвертер до того, как TextTrimming="WordEllipsis" был добавлен в Silverlight 4. Вы можете найти его здесь: http://weblogs.asp.net/dwahlin/archive/2010/05/05/text-trimming-in-silverlight-4.aspx

Вот как я обошел отсутствие опции CharacterEllipsis. Мое решение тоже не идеально, но оно до сих пор работало для меня.

Сначала я добавил следующий вспомогательный метод:

public static void AutoTrimTextBlock(TextBlock textBlock, double maxWidth)
{
    if (!string.IsNullOrWhiteSpace(textBlock.Text))
    {
        var currentWidth = textBlock.ActualWidth;
        if (currentWidth > maxWidth)
        {
            if (textBlock.Text.Length > 2)
            {
                int substrLength = textBlock.Text.Length - 1;
                if (textBlock.Text[substrLength] == '…')
                    substrLength--;
                textBlock.Text = textBlock.Text.Substring(0, substrLength) + '…';
            }
            else if (textBlock.Text.Length == 2)
            {
                if (textBlock.Text[1] == '…')
                    textBlock.Text = "…";
                else
                    textBlock.Text = textBlock.Text[0].ToString() + '…';
            }
            else //implies: if (length == 1)
            {
                textBlock.Text = string.Empty;
            }
        }
    }
}

Затем я обновил свой XAML, чтобы он выглядел так:

<Grid x:Name="MyGrid">
    <Grid.ColumnDefinitions>
        <ColumnDefinition x:Name="Column0" Width="Auto"/>
        <ColumnDefinition x:Name="Column1" Width="*"/>
    </Grid.ColumnDefinitions>

    <TextBlock Grid.Column="0" x:Name="SomeOtherText" Text="{Binding OtherString}"/>

    <TextBlock Grid.Column="1" x:Name="MyTextBlock"
               TextWrapping="NoWrap"                        <!--Disable text wrapping-->
               TextTrimming="None"                          <!--Disable built-in text trimming-->
               Text="{Binding MyString, Mode=OneWay}"       <!--OneWay binding avoids writing trimmed text back to view model-->
               LayoutUpdated="MyTextBlock_LayoutUpdated"/>  <!--LayoutUpdated event will trigger custom text trimming-->
</Grid>

Наконец, в коде я добавил следующее:

void MyTextBlock_LayoutUpdated(object sender, System.EventArgs e)
{
    // Calculate maximum width for MyTextBlock.
    // I did it by checking the parent column width,
    // but you can do it any way you like.
    double maxWidth = Column1.ActualWidth - MyTextBlock.Margin.Left - MyTextBlock.Margin.Right;

    // Start trimming
    AutoTrimTextBlock(MyTextBlock, maxWidth);
}

Результат: при каждом изменении свойства MyString вызывается обработчик события LayoutUpdated и вызывается метод AutoTrimTextBlock(). Если MyTextBlock слишком широкий, его свойство Text обрезается и добавляется "…". Это вызывает другое событие LayoutUpdated. Процесс повторяется до тех пор, пока ширина MyTextBlock не станет меньше указанного максимума.

Как я уже сказал, он не идеален и не особенно элегантен, но в примерах, подобных приведенным выше, работает нормально.

Мне не нравится идея использовать событие LayoutUpdated, но я не смог найти другое подходящее. TextChanged не существует для TextBlock, к сожалению:(

Пожалуйста, дайте мне знать, если я могу что-то улучшить.

    private bool TrimExtraCharacters(TextBlock textBlock)
    {
        if (textBlock != null && textBlock.ActualWidth > 0.1 && !string.IsNullOrWhiteSpace(textBlock.Text))
        {
            if (textBlock.ActualWidth > textBlock.MaxWidth)
            {
                textBlock.Text += '…';
                int lastLetterIndex = textBlock.Text.Length -2;
                do
                {
                    textBlock.Text = textBlock.Text.Remove(lastLetterIndex, 1);
                    --lastLetterIndex;
                } while (textBlock.ActualWidth > textBlock.MaxWidth);
            }
            return true;
        }
        return false;
    }
Другие вопросы по тегам