القطع في بداية السلسلة في WPF ListView
سؤال
لدي WPF ListView
(GridView
) و الخلية قالب يحتوي على TextBlock
.إذا كان لي أن أضيف: TextTrimming="CharacterEllipsis" TextWrapping="NoWrap"
على TextBlock
, القطع سوف تظهر في نهاية سلسلة بلدي عندما العمود يحصل أصغر من طول السلسلة.ما أريده هو أن يكون الحذف في بداية السلسلة.
أولا-هاء.إذا كان لدي سلسلة Hello World!
, أود ...lo World!
, بدلا من Hello W...
.
أي أفكار ؟
المحلول
هل يمكن أن محاولة استخدام ValueConverter (راجع <لأ href = "http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx" يختلط = "نوفولو noreferrer" > IValueConverter م> واجهة ) لتغيير السلاسل التي يجب أن يتم عرضها في قائمة مربع نفسك. وهذا هو، في تنفيذ الأسلوب تحويل، هل اختبار إذا سلاسل أطول من المساحة المتوفرة، ومن ثم تغييرها إلى ... بالإضافة إلى الجانب الأيمن من السلسلة.
نصائح أخرى
وكنت أواجه نفس المشكلة وكتب خاصية تعلق على حل هذه (أو القول، وتوفير هذه الميزة). تبرع قانون بلدي هنا:
استعمال
<controls:TextBlockTrimmer EllipsisPosition="Start">
<TextBlock Text="Excuse me but can I be you for a while"
TextTrimming="CharacterEllipsis" />
</controls:TextBlockTrimmer>
لا تنس أن تضيف إعلانا مساحة في الصفحة الخاصة بك / نافذة الجذر / UserControl:
xmlns:controls="clr-namespace:Hillinworks.Wpf.Controls"
ويمكن TextBlockTrimmer.EllipsisPosition
Start
، Middle
(نمط ماك) أو End
. متأكد يمكنك معرفة ما هو الذي من أسمائهم.
CODE
TextBlockTrimmer.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
namespace Hillinworks.Wpf.Controls
{
enum EllipsisPosition
{
Start,
Middle,
End
}
[DefaultProperty("Content")]
[ContentProperty("Content")]
internal class TextBlockTrimmer : ContentControl
{
private class TextChangedEventScreener : IDisposable
{
private readonly TextBlockTrimmer _textBlockTrimmer;
public TextChangedEventScreener(TextBlockTrimmer textBlockTrimmer)
{
_textBlockTrimmer = textBlockTrimmer;
s_textPropertyDescriptor.RemoveValueChanged(textBlockTrimmer.Content,
textBlockTrimmer.TextBlock_TextChanged);
}
public void Dispose()
{
s_textPropertyDescriptor.AddValueChanged(_textBlockTrimmer.Content,
_textBlockTrimmer.TextBlock_TextChanged);
}
}
private static readonly DependencyPropertyDescriptor s_textPropertyDescriptor =
DependencyPropertyDescriptor.FromProperty(TextBlock.TextProperty, typeof(TextBlock));
private const string ELLIPSIS = "...";
private static readonly Size s_inifinitySize = new Size(double.PositiveInfinity, double.PositiveInfinity);
public EllipsisPosition EllipsisPosition
{
get { return (EllipsisPosition)GetValue(EllipsisPositionProperty); }
set { SetValue(EllipsisPositionProperty, value); }
}
public static readonly DependencyProperty EllipsisPositionProperty =
DependencyProperty.Register("EllipsisPosition",
typeof(EllipsisPosition),
typeof(TextBlockTrimmer),
new PropertyMetadata(EllipsisPosition.End,
TextBlockTrimmer.OnEllipsisPositionChanged));
private static void OnEllipsisPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((TextBlockTrimmer)d).OnEllipsisPositionChanged((EllipsisPosition)e.OldValue,
(EllipsisPosition)e.NewValue);
}
private string _originalText;
private Size _constraint;
protected override void OnContentChanged(object oldContent, object newContent)
{
var oldTextBlock = oldContent as TextBlock;
if (oldTextBlock != null)
{
s_textPropertyDescriptor.RemoveValueChanged(oldTextBlock, TextBlock_TextChanged);
}
if (newContent != null && !(newContent is TextBlock))
// ReSharper disable once LocalizableElement
throw new ArgumentException("TextBlockTrimmer access only TextBlock content", nameof(newContent));
var newTextBlock = (TextBlock)newContent;
if (newTextBlock != null)
{
s_textPropertyDescriptor.AddValueChanged(newTextBlock, TextBlock_TextChanged);
_originalText = newTextBlock.Text;
}
else
_originalText = null;
base.OnContentChanged(oldContent, newContent);
}
private void TextBlock_TextChanged(object sender, EventArgs e)
{
_originalText = ((TextBlock)sender).Text;
this.TrimText();
}
protected override Size MeasureOverride(Size constraint)
{
_constraint = constraint;
return base.MeasureOverride(constraint);
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
var result = base.ArrangeOverride(arrangeBounds);
this.TrimText();
return result;
}
private void OnEllipsisPositionChanged(EllipsisPosition oldValue, EllipsisPosition newValue)
{
this.TrimText();
}
private IDisposable BlockTextChangedEvent()
{
return new TextChangedEventScreener(this);
}
private static double MeasureString(TextBlock textBlock, string text)
{
textBlock.Text = text;
textBlock.Measure(s_inifinitySize);
return textBlock.DesiredSize.Width;
}
private void TrimText()
{
var textBlock = (TextBlock)this.Content;
if (textBlock == null)
return;
if (DesignerProperties.GetIsInDesignMode(textBlock))
return;
var freeSize = _constraint.Width
- this.Padding.Left
- this.Padding.Right
- textBlock.Margin.Left
- textBlock.Margin.Right;
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (freeSize <= 0)
return;
using (this.BlockTextChangedEvent())
{
// this actually sets textBlock's text back to its original value
var desiredSize = TextBlockTrimmer.MeasureString(textBlock, _originalText);
if (desiredSize <= freeSize)
return;
var ellipsisSize = TextBlockTrimmer.MeasureString(textBlock, ELLIPSIS);
freeSize -= ellipsisSize;
var epsilon = ellipsisSize / 3;
if (freeSize < epsilon)
{
textBlock.Text = _originalText;
return;
}
var segments = new List<string>();
var builder = new StringBuilder();
switch (this.EllipsisPosition)
{
case EllipsisPosition.End:
TextBlockTrimmer.TrimText(textBlock, _originalText, freeSize, segments, epsilon, false);
foreach (var segment in segments)
builder.Append(segment);
builder.Append(ELLIPSIS);
break;
case EllipsisPosition.Start:
TextBlockTrimmer.TrimText(textBlock, _originalText, freeSize, segments, epsilon, true);
builder.Append(ELLIPSIS);
foreach (var segment in ((IEnumerable<string>)segments).Reverse())
builder.Append(segment);
break;
case EllipsisPosition.Middle:
var textLength = _originalText.Length / 2;
var firstHalf = _originalText.Substring(0, textLength);
var secondHalf = _originalText.Substring(textLength);
freeSize /= 2;
TextBlockTrimmer.TrimText(textBlock, firstHalf, freeSize, segments, epsilon, false);
foreach (var segment in segments)
builder.Append(segment);
builder.Append(ELLIPSIS);
segments.Clear();
TextBlockTrimmer.TrimText(textBlock, secondHalf, freeSize, segments, epsilon, true);
foreach (var segment in ((IEnumerable<string>)segments).Reverse())
builder.Append(segment);
break;
default:
throw new NotSupportedException();
}
textBlock.Text = builder.ToString();
}
}
private static void TrimText(TextBlock textBlock,
string text,
double size,
ICollection<string> segments,
double epsilon,
bool reversed)
{
while (true)
{
if (text.Length == 1)
{
var textSize = TextBlockTrimmer.MeasureString(textBlock, text);
if (textSize <= size)
segments.Add(text);
return;
}
var halfLength = Math.Max(1, text.Length / 2);
var firstHalf = reversed ? text.Substring(halfLength) : text.Substring(0, halfLength);
var remainingSize = size - TextBlockTrimmer.MeasureString(textBlock, firstHalf);
if (remainingSize < 0)
{
// only one character and it's still too large for the room, skip it
if (firstHalf.Length == 1)
return;
text = firstHalf;
continue;
}
segments.Add(firstHalf);
if (remainingSize > epsilon)
{
var secondHalf = reversed ? text.Substring(0, halfLength) : text.Substring(halfLength);
text = secondHalf;
size = remainingSize;
continue;
}
break;
}
}
}
}
للأسف هذا غير ممكن في WPF اليوم كما ترون من الوثائق.
(كنت أعمل في شركة مايكروسوفت على WPF, كان هذا ميزة ونحن للأسف لم تحصل للقيام -- لست متأكدا إذا كان يعتزم إصدار لاحق)
لقد نفذت (نسخ) أعلاه TextBlockTrimmer
رمز وعملت كبيرة من أجل التحميل ولكن TextBlock.Text
سوف يتم تحديث بعد ذلك ، إذا لا بد أن عرض نموذج الملكية التي تغيرت.ماذا وجدت أن يعمل هو
- تعريف DependencyProperty يسمى
TextBlockText
فيTextBlockTrimmer
, ، على غرارEllipsisPosition
الملكية أعلاه ، بما في ذلكOnTextBlockTextChanged()
الأسلوب. - في
OnTextBlockTextChanged()
طريقة تعيين_originalText
إلىnewValue
قبل استدعاءTrimText()
. - ربط
TextBlockText
خاصية عرض نموذج الملكية (يسمىSomeText
في XAML أدناه) ربط
TextBlock.Text
الممتلكاتTextBlockTrimmer.TextBlockText
الملكية في XAML:<controls:TextBlockTrimmer EllipsisPosition="Middle" TextBlockText="{Binding SomeText, Mode=OneWay}" <TextBlock Text="{Binding TextBlockText, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type controls:TextBlockTrimmer}}}" HorizontalAlignment="Stretch"/> </controls:TextBlockTrimmer>
كما عمل إذا أنا ملزمة على حد سواء TextBlockTrimmer.TextBlockText
و TextBlock.Text
إلى SomeText
(ولكن ذلك الخلل مني).
وهنا مثال كيفية القيام قطة النص فعالة مع خوارزمية لوغاريتمي متكررة:
private static string ClipTextToWidth(
TextBlock reference, string text, double maxWidth)
{
var half = text.Substring(0, text.Length/2);
if (half.Length > 0)
{
reference.Text = half;
var actualWidth = reference.ActualWidth;
if (actualWidth > maxWidth)
{
return ClipTextToWidth(reference, half, maxWidth);
}
return half + ClipTextToWidth(
reference,
text.Substring(half.Length, text.Length - half.Length),
maxWidth - actualWidth);
}
return string.Empty;
}
وافترض أن لديك حقل TextBlock
اسمه textBlock
، وتريد مقطع النص فيه في عرض أقصى معين، مع القطع إلحاق. المكالمات الطريقة التالية ClipTextToWidth
لضبط النص للحقل textBlock
:
public void UpdateTextBlock(string text, double maxWidth)
{
if (text != null)
{
this.textBlock.Text = text;
if (this.textBlock.ActualWidth > maxWidth)
{
this.textBlock.Text = "...";
var ellipsisWidth = this.textBlock.ActualWidth;
this.textBlock.Text = "..." + ClipTextToWidth(
this.textBlock, text, maxWidth - ellipsisWidth);
}
}
else
{
this.textBlock.Text = string.Empty;
}
}
وعلى أمل أن يساعد!
في حالة شخص آخر يتعثر على هذا السؤال كما فعلت هنا موضوع آخر مع أفضل إجابة (لا تأخذ الائتمان):