TextBlock with ellipsis at left or center
If you want a TextBlock with ellipsis a Left, Center or Right use this
you must use TrimmedText in place of Text for binding original text
[Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)]
public enum EllipsisPosition
{
//
// Résumé :
// Par défaut. elispsis sur la droite.
Right = 0,
//
// Résumé :
// elispsis sur la gauche
Left = 1,
//
// Résumé :
// elispsis an centre.
Center = 2,
}
public class TextBlockTrim : TextBlock
{
private const string ELLIPSIS = "...";
#region static
static TextBlockTrim()
{
TextAlignmentProperty.OverrideMetadata(typeof(TextBlockTrim),
new FrameworkPropertyMetadata(
TextAlignment.Left,
FrameworkPropertyMetadataOptions.Inherits,
(sender, e) => ((TextBlockTrim)sender)._Changed(e)));
VisibilityProperty.OverrideMetadata(typeof(TextBlockTrim),
new FrameworkPropertyMetadata(
Visibility.Visible,
FrameworkPropertyMetadataOptions.Inherits,
(sender, e) => ((TextBlockTrim)sender)._visibility(e)));
}
#endregion
public TextBlockTrim()
{
this.SizeChanged += (ss, ee) => UpdateText();
this.TextTrimming = TextTrimming.CharacterEllipsis;
}
#region TextAlignmentChanged
private bool protect = false;
private void _Changed(DependencyPropertyChangedEventArgs e)
{
if (protect) return;
switch (e.NewValue)
{
case TextAlignment ta:
previousTextAligment = ta;
UpdateText();
break;
case string s:
UpdateText(s);
break;
case EllipsisPosition ep:
UpdateText();
break;
default:
throw new Exception("Unknow type");
}
}
#endregion
#region VisibilityChanged
private void _visibility(DependencyPropertyChangedEventArgs e)
{
UpdateText();
}
#endregion
#region EllipsisPosition
public EllipsisPosition EllipsisPosition
{
get { return (EllipsisPosition)GetValue(EllipsisPositionProperty); }
set { SetValue(EllipsisPositionProperty, value); }
}
// Using a DependencyProperty as the backing store for EllipsisPosition. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EllipsisPositionProperty =
DependencyProperty.Register("EllipsisPosition",
typeof(EllipsisPosition),
typeof(TextBlockTrim),
new FrameworkPropertyMetadata(EllipsisPosition.Right,
(sender, e) => ((TextBlockTrim)sender)._Changed(e)));
#endregion
#region TrimmedText (DP SHORT)
public string TrimmedText { get { return (string)GetValue(TrimmedTextProperty); } set { SetValue(TrimmedTextProperty, value); } }
public static readonly DependencyProperty TrimmedTextProperty = DependencyProperty.Register(
"TrimmedText",
typeof(string),
typeof(TextBlockTrim),
new FrameworkPropertyMetadata(null,
(sender, e) => ((TextBlockTrim)sender)._Changed(e)));
#endregion
#region UpdateText
string previousText = null;
TextAlignment previousTextAligment = TextAlignment.Left;
private void UpdateText(string newText = null)
{
if (newText != null)
previousText = newText;
if (previousText == null)
{
Text = null;
return;
}
if (this.ActualWidth == 0) return;
try
{
string str = previousText;
if (!string.IsNullOrEmpty(previousText))
{
double w = MeasureText(previousText, this).Width;
double width = ActualWidth;
if (w > width && width != 0)
{
ToolTip = previousText;
switch (EllipsisPosition)
{
case EllipsisPosition.Right:
{
do
{
str = str.Substring(1);
w = MeasureText(ELLIPSIS + str, this).Width;
} while (w > width);
str = ELLIPSIS + str;
protect = true;
TextAlignment = TextAlignment.Left;
protect = false;
}
break;
case EllipsisPosition.Left:
{
int size = str.Length;
do
{
str = str.Substring(0, size);
w = MeasureText(str + ELLIPSIS, this).Width;
size--;
} while (w > width);
str = str + ELLIPSIS;
protect = true;
TextAlignment = TextAlignment.Right;
protect = false;
}
break;
case EllipsisPosition.Center:
{
string l, r, t;
{
int size = str.Length / 2;
int pos = size;
do
{
l = str.Substring(0, size);
r = str.Substring(pos);
t = l + ELLIPSIS + r;
w = MeasureText(t, this).Width;
size--; pos++;
} while (w > width);
str = t;
}
protect = true;
TextAlignment = TextAlignment.Center;
protect = false;
}
break;
}
}
else
{
ToolTip = null;
protect = true;
TextAlignment = previousTextAligment;
protect = false;
}
}
Text = str;
}
catch { Text = null; }
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
UpdateText();
}
#endregion
#region Static functions
public static Size MeasureText(string text, TextBlock textBlock)
{
return MeasureText(text, textBlock.FontFamily,
textBlock.FontStyle,
textBlock.FontWeight,
textBlock.FontStretch,
textBlock.FontSize,
textBlock);
}
public static Size MeasureText(string text,
FontFamily fontFamily,
FontStyle fontStyle,
FontWeight fontWeight,
FontStretch fontStretch,
double fontSize,
Visual reference)
{
Typeface typeface = new Typeface(fontFamily, fontStyle, fontWeight, fontStretch);
GlyphTypeface glyphTypeface;
if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
{
return MeasureTextSize(text, fontFamily, fontStyle, fontWeight, fontStretch, fontSize, reference);
}
double totalWidth = 0;
double height = 0;
for (int n = 0; n < text.Length; n++)
{
ushort glyphIndex = glyphTypeface.CharacterToGlyphMap];
double width = glyphTypeface.AdvanceWidths[glyphIndex] * fontSize;
double glyphHeight = glyphTypeface.AdvanceHeights[glyphIndex] * fontSize;
if (glyphHeight > height)
{
height = glyphHeight;
}
totalWidth += width;
}
return new Size(totalWidth, height);
}
public static Size MeasureTextSize(string text,
FontFamily fontFamily,
FontStyle fontStyle,
FontWeight fontWeight,
FontStretch fontStretch,
double fontSize,
Visual reference)
{
FormattedText ft = new FormattedText(text,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(fontFamily, fontStyle, fontWeight, fontStretch),
fontSize,
Brushes.Black,
VisualTreeHelper.GetDpi(reference).PixelsPerDip);
return new Size(ft.Width, ft.Height);
}
#endregion
}
and use like this
<Grid>
<TextBlock HorizontalAlignment="Left"
Margin="10,18,0,0"
TextWrapping="Wrap"
Text="elipsis left"
FontWeight="Bold"
VerticalAlignment="Top"
Width="118" />
<TextBlockTrim Background="Aqua"
TrimmedText="123456789 123456789 123456789 123456789"
Margin="10,39,9.6,0"
EllipsisPosition="Left"
TextWrapping="Wrap"
VerticalAlignment="Top"
Visibility="Visible" />
<TextBlockTrim Background="Aqua"
TrimmedText="123456789 123456789 123456789 123456789"
TextAlignment="Center"
EllipsisPosition="Left"
Margin="10,60,9.6,0"
TextWrapping="Wrap"
VerticalAlignment="Top"
Visibility="Visible" />
<TextBlockTrim Background="Aqua"
TrimmedText="123456789 123456789 123456789 123456789"
Margin="10,81,9.6,0"
TextAlignment="Right"
EllipsisPosition="Left"
TextWrapping="Wrap"
VerticalAlignment="Top"
Visibility="Visible" />
<TextBlock HorizontalAlignment="Left"
Margin="10,108,0,0"
TextWrapping="Wrap"
Text="elipsis right"
FontWeight="Bold"
VerticalAlignment="Top"
Width="118" />
<TextBlockTrim Background="Aqua"
TrimmedText="123456789 123456789 123456789 123456789"
Margin="10,130,9.6,0"
TextWrapping="Wrap"
VerticalAlignment="Top"
Visibility="Visible" />
<TextBlockTrim Background="Aqua"
TrimmedText="123456789 123456789 123456789 123456789"
TextAlignment="Center"
Margin="10,151,9.6,0"
TextWrapping="Wrap"
VerticalAlignment="Top"
Visibility="Visible" />
<TextBlockTrim Background="Aqua"
TrimmedText="123456789 123456789 123456789 123456789"
Margin="10,172,9.6,0"
TextAlignment="Right"
TextWrapping="Wrap"
VerticalAlignment="Top"
Visibility="Visible" />
<TextBlock HorizontalAlignment="Left"
Margin="10,204,0,0"
TextWrapping="Wrap"
Text="elipsis center"
FontWeight="Bold"
VerticalAlignment="Top"
Width="118" />
<TextBlockTrim Background="Aqua"
TrimmedText="123456789 123456789 123456789 123456789"
Margin="10,226,9.6,0"
EllipsisPosition="Center"
TextWrapping="Wrap"
VerticalAlignment="Top" />
<TextBlockTrim Background="Aqua"
TrimmedText="123456789 123456789 123456789 123456789"
EllipsisPosition="Center"
TextAlignment="Center"
Margin="10,247,9.6,0"
TextWrapping="Wrap"
VerticalAlignment="Top"
Visibility="Visible" />
<TextBlockTrim Background="Aqua"
TrimmedText="123456789 123456789 123456789 123456789"
Margin="10,268,9.6,0"
EllipsisPosition="Center"
TextAlignment="Right"
TextWrapping="Wrap"
VerticalAlignment="Top"
Visibility="Visible" />
</Grid>
see result

Update to v2 :
refresh when visibility changed
refresh when size changed
