我使用列表框显示带有数据模板的撤消重做列表:
<ListBox x:Name="actionList"
Height="150"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MouseMove="ListBoxMouseMove"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectionMode="Extended"
Style="{StaticResource CustomListBoxStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Width="235"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
FontSize="11"
Text="{Binding DisplayText}"
TextWrapping="Wrap" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
它正在按预期工作,但是对于长撤消重做字符串,会发生包装但它与行的第一个字符对齐。我们希望它缩进一点,以清楚地识别两个列表项。说明如下:
我们怎样才能实现同样的目标。
我找到了使用转换器的解决方法:
<ListBox x:Name="actionList"
Height="150"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MouseMove="ListBoxMouseMove"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectionMode="Extended"
Style="{StaticResource CustomListBoxStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="textBlock"
Width="235"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
FontSize="11"
Text="{Binding DisplayText,
Converter={StaticResource indentWrapConverter},
ConverterParameter=235}"
TextWrapping="Wrap"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
转换器类:
namespace XXXX.Converters
{
using System;
using System.Text;
using System.Globalization;
using System.Windows.Data;
/// <summary>
/// Wrap converter for text block to indent wrapped line a bit to identify
/// it correctly
/// </summary>
public class IndentWrapConverter : IValueConverter
{
#region Implementation of IValueConverter
/// <summary>
/// Wrap the passed-in text as per passed-in textblock width, after warpping it
/// adds whitepsaces to indent the next lines
/// </summary>
/// <returns>
/// A converted value. If the method returns null, the valid null value is used.
/// </returns>
/// <param name="value">The value produced by the binding source.</param><param name="targetType">The type of the binding target property.</param><param name="parameter">The converter parameter to use.</param><param name="culture">The culture to use in the converter.</param>
public object Convert(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
// get the string of textblock
var stringData = value as string;
// if string cant be parsed retrurn null
if (stringData == null)
{
return null;
}
// get textblock width
var width = 235.0;
double.TryParse(parameter.ToString(),
out width);
// get actual length by using text block width divide by font size
int length = (int)Math.Round((2 * width / 11));
// if text length is less than calulated length then return string as it is
// else wrap the text
return stringData.Length > length
? WordWrap(stringData,
length)
: stringData;
}
/// <summary>
/// Word wraps the given text to fit within the specified width.
/// </summary>
/// <param name="text">Text to be word wrapped</param>
/// <param name="width">Width, in characters, to which the text
/// should be word wrapped</param>
/// <returns>The modified text</returns>
private static string WordWrap(string text,
int width)
{
int pos, next;
StringBuilder sb = new StringBuilder();
// Lucidity check
if (width < 1)
return text;
// Parse each line of text
for (pos = 0; pos < text.Length; pos = next)
{
// Find end of line
int eol = text.IndexOf(Environment.NewLine,
pos,
StringComparison.Ordinal);
if (eol == -1)
next = eol = text.Length;
else
next = eol + Environment.NewLine.Length;
// Copy this line of text, breaking into smaller lines as needed
if (eol > pos)
{
do
{
int len = eol - pos;
if (len > width)
len = BreakLine(text, pos, width);
sb.Append(text, pos, len);
if (pos < width)
{
sb.Append(Environment.NewLine);
sb.Append(" ");
}
// Trim whitespace following break
pos += len;
while (pos < eol && Char.IsWhiteSpace(text[pos]))
pos++;
} while (eol > pos);
}
}
return sb.ToString();
}
/// <summary>
/// Locates position to break the given line so as to avoid
/// breaking words.
/// </summary>
/// <param name="text">String that contains line of text</param>
/// <param name="pos">Index where line of text starts</param>
/// <param name="max">Maximum line length</param>
/// <returns>The modified line length</returns>
private static int BreakLine(string text, int pos, int max)
{
// Find last whitespace in line
int i = max;
while (i >= 0 && !Char.IsWhiteSpace(text[pos + i]))
i--;
// If no whitespace found, break at maximum length
if (i < 0)
return max;
// Find start of whitespace
while (i >= 0 && Char.IsWhiteSpace(text[pos + i]))
i--;
// Return length of text before whitespace
return i + 1;
}
/// <summary>
/// Converts a value.
/// </summary>
/// <returns>
/// A converted value. If the method returns null, the valid null value is used.
/// </returns>
/// <param name="value">The value that is produced by the binding target.</param><param name="targetType">The type to convert to.</param><param name="parameter">The converter parameter to use.</param><param name="culture">The culture to use in the converter.</param>
public object ConvertBack(object value,
Type targetType,
object parameter,
CultureInfo culture)
{
return null;
}
#endregion
}
}
根据需要使用Listbox样式和选择。
他们“更容易”的方式可能涉及使用Paragraph对象来表示文本。
段落本身支持缩进,使用TextIndent等属性(控制第一行缩进,可以将其设置为负值)或边距(为整个段落设置边距,但尊重第一行缩进)。
<ListBox.ItemTemplate>
<DataTemplate>
// IsHitTestVisible is set to false to avoid FlowDocument's built-in text selection
// from disrupting the regular ListBox mouse selection behavior
<Grid IsHitTestVisible="False">
<FlowDocumentScrollViewer ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<FlowDocument FontSize="12"
FontFamily="Calibri"
Foreground="Black"
PagePadding="0">
<Paragraph TextIndent="-10"
Margin="10,0,0,0">
<Run Text="{Binding ., Mode=OneWay}" />
</Paragraph>
</FlowDocument>
</FlowDocumentScrollViewer>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>