Replacement for missing TextTrimming option "CharacterEllipsis" in Silverlight
Silverlight (at least, as of Version 4) has no CharacterEllipsis
option for TextTrimming
, which WPF has. It could be used on a TextBlock
. That means, If there is not enough room to display "That's incredible", I could trim to "That's..." but not to "That's incred..." which we'd rather want.
I though, we'd try to implement our custom text trimming function. Basically, thats not that hard. A quite stupid way is to measure the pixels for a string, compare to the available width and manipulate the string by cutting last character and adding "..." in a loop while the text still doesn't fit. Here is an example how this could work:
// Not perfect but good enough for us
private bool AutoTrim(string fullText, TextBlock textBlock, double maxWidth)
{
double fa开发者_StackOverflow社区ctor = 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;
}
But doing that in code behind (or within a Behavior
) leads to some problems: For example, when we want to update the displayed text and set the TextBlock's TextBlock1.Text = ...
Property, it actually might change our viewModel if the Text is bound to a ViewModel Property. Another problems occur as we noticed that view and viewModel might run of of sync for some reason (we noticed that in a ListBox).
Do you have a better idea on how to solve this problem in a good way?
Robby Ingebretsen's DynamicTextBox does this by wrapping the TextBlock in a custom control and measuring the available size. It matches the CharacterEllipsis text trimming mode of WPF. WordEllipsis mode did get added to Windows Phone 7 Mango, but that isn't much help here.
Dan Wahlin used a converter before TextTrimming="WordEllipsis" was added to Silverlight 4. You can find it here: http://weblogs.asp.net/dwahlin/archive/2010/05/05/text-trimming-in-silverlight-4.aspx
Here's the way I worked around the lack of a CharacterEllipsis option. My solution is not perfect either, but it has worked for me so far.
First, I added the following helper method:
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;
}
}
}
}
Then I updated my XAML to look like this:
<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>
Finally, in the code behind I added the following:
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);
}
The result: whenever the MyString property changes, the LayoutUpdated event handler is fired and the AutoTrimTextBlock() method is called. If MyTextBlock is too wide, its Text property it is trimmed and '…' is appended. This causes another LayoutUpdated event. The process repeats until the width of MyTextBlock is less than the specified maximum.
As I said, it's not perfect and not particularly elegant, but it works OK in examples like the above.
I don't like the idea of using the LayoutUpdated event, but I couldn't find another suitable one. TextChanged does not exist for TextBlock, unfortunately :(
Please let me know if there is anything I can improve.
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;
}
精彩评论