Количество видимых строк TextBlock - PullRequest
8 голосов
/ 09 июля 2009

Если для TextWrapping установлено значение «Обтекание», текстовый блок WPF может содержать несколько строк текста. Есть ли «чистый» способ получить количество строк текста? Я подумал о том, чтобы посмотреть на желаемую высоту и разделить ее на предполагаемую высоту каждой линии. Однако это выглядит довольно грязно. Есть ли лучший способ?

Ответы [ 4 ]

8 голосов
/ 10 июля 2009

В WPF очень приятно то, что все элементы управления выглядят очень неприглядно. Из-за этого мы можем использовать TextBox , который имеет свойство LineCount (почему это не DependencyProperty или почему TextBlock также не имеет его, я не знаю). С помощью TextBox мы можем просто изменить его шаблон, чтобы он вел себя и выглядел больше как TextBlock. В нашем пользовательском стиле / шаблоне мы собираемся установить для IsEnabled значение False и просто создать базовый повторный шаблон элемента управления, чтобы отключенный вид больше не присутствовал. Мы также можем связать любые свойства, которые мы хотим сохранить, например, Background, используя TemplateBindings.

<Style x:Key="Local_TextBox"
    TargetType="{x:Type TextBoxBase}">
    <Setter Property="IsEnabled"
            Value="False" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TextBoxBase}">
                <Border Name="Border"
                    Background="{TemplateBinding Background}">
                    <ScrollViewer x:Name="PART_ContentHost" />
                </Border>
            </ControlTemplate>
        </Setter.Value>
</Setter>
</Style>

Теперь мы позаботимся о том, чтобы наш TextBox выглядел и вел себя как TextBlock, но как мы можем получить количество строк?

Что ж, если мы хотим получить к нему доступ непосредственно в коде, то мы можем зарегистрироваться в событии SizeChanged TextBox.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        LongText = "This is a long line that has lots of text in it.  Because it is a long line, if a TextBlock's TextWrapping property is set to wrap then the text will wrap onto new lines. However, we can also use wrapping on a TextBox, that has some diffrent properties availible and then re-template it to look just like a TextBlock!";

        uiTextBox.SizeChanged += new SizeChangedEventHandler(uiTextBox_SizeChanged);

        this.DataContext = this;
    }

    void uiTextBox_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        Lines = uiTextBox.LineCount;
    }

    public string LongText { get; set; }

    public int Lines
    {
        get { return (int)GetValue(LinesProperty); }
        set { SetValue(LinesProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Lines.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty LinesProperty =
        DependencyProperty.Register("Lines", typeof(int), typeof(MainWindow), new UIPropertyMetadata(-1));
}

Однако, поскольку я склонен использовать такие свойства в местах, отличных от текущего окна, и / или использую MVVM и не хочу использовать этот подход, то мы можем создать некоторые AttachedProperties для обработки поиска и настройка LineCount. Мы собираемся использовать AttachedProperties, чтобы сделать то же самое, но теперь мы сможем использовать его с любым TextBox в любом месте и привязать к нему через этот TextBox вместо DataContext окна.

public class AttachedProperties
{
    #region BindableLineCount AttachedProperty
    public static int GetBindableLineCount(DependencyObject obj)
    {
        return (int)obj.GetValue(BindableLineCountProperty);
    }

    public static void SetBindableLineCount(DependencyObject obj, int value)
    {
        obj.SetValue(BindableLineCountProperty, value);
    }

    // Using a DependencyProperty as the backing store for BindableLineCount.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty BindableLineCountProperty =
        DependencyProperty.RegisterAttached(
        "BindableLineCount",
        typeof(int),
        typeof(MainWindow),
        new UIPropertyMetadata(-1));

    #endregion // BindableLineCount AttachedProperty

    #region HasBindableLineCount AttachedProperty
    public static bool GetHasBindableLineCount(DependencyObject obj)
    {
        return (bool)obj.GetValue(HasBindableLineCountProperty);
    }

    public static void SetHasBindableLineCount(DependencyObject obj, bool value)
    {
        obj.SetValue(HasBindableLineCountProperty, value);
    }

    // Using a DependencyProperty as the backing store for HasBindableLineCount.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty HasBindableLineCountProperty =
        DependencyProperty.RegisterAttached(
        "HasBindableLineCount",
        typeof(bool),
        typeof(MainWindow),
        new UIPropertyMetadata(
            false,
            new PropertyChangedCallback(OnHasBindableLineCountChanged)));

    private static void OnHasBindableLineCountChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var textBox = (TextBox)o;
        if ((e.NewValue as bool?) == true)
        {
            textBox.SetValue(BindableLineCountProperty, textBox.LineCount);
            textBox.SizeChanged += new SizeChangedEventHandler(box_SizeChanged);
        }
        else
        {
            textBox.SizeChanged -= new SizeChangedEventHandler(box_SizeChanged);
        }
    }

    static void box_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        var textBox = (TextBox)sender;
        (textBox).SetValue(BindableLineCountProperty, (textBox).LineCount);
    }
    #endregion // HasBindableLineCount AttachedProperty
}

Теперь найти LineCount просто:

<StackPanel>
    <TextBox x:Name="uiTextBox"
             TextWrapping="Wrap"
             local:AttachedProperties.HasBindableLineCount="True"
             Text="{Binding LongText}"
             Style="{StaticResource Local_TextBox}" />

    <TextBlock Text="{Binding Lines, StringFormat=Binding through the code behind: {0}}" />
    <TextBlock Text="{Binding ElementName=uiTextBox, Path=(local:AttachedProperties.BindableLineCount), StringFormat=Binding through AttachedProperties: {0}}" />
</StackPanel>
3 голосов
/ 20 апреля 2012
// this seems to do the job        

<TextBox x:Name="DescriptionTextBox"
                         Grid.Row="03"
                         Grid.RowSpan="3"
                         Grid.Column="01"
                         Width="100"
                         AcceptsReturn="True"
                         MaxLength="100"
                         MaxLines="3"
                         PreviewKeyDown="DescriptionTextBox_PreviewKeyDown"
                         Text="{Binding Path=Description,
                                        Mode=TwoWay,
                                        UpdateSourceTrigger=PropertyChanged}"
                         TextWrapping="Wrap" />



        /// <summary>
        /// we need to limit a multi line textbox at entry time
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private void DescriptionTextBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            TextBox thisTextBox = sender as TextBox;
            if (thisTextBox != null)
            {
                // only check if we have passed the MaxLines 
                if (thisTextBox.LineCount > thisTextBox.MaxLines)
                {
                    // we are going to discard the last entered character
                    int numChars = thisTextBox.Text.Length;

                    // force the issue
                    thisTextBox.Text = thisTextBox.Text.Substring(0, numChars - 1);

                    // set the cursor back to the last allowable character
                    thisTextBox.SelectionStart = numChars - 1;

                    // disallow the key being passed in
                    e.Handled = true;
                }
            }
        }
1 голос
/ 08 февраля 2017

Я видел, что этому вопросу уже 7 лет, но я только что нашел решение:

TextBlock имеет частное свойство LineCount. Я создал метод расширения для чтения этого значения:

public static class TextBlockExtension
{
    public static int GetLineCount(this TextBlock tb)
    {
        var propertyInfo = GetPrivatePropertyInfo(typeof(TextBlock), "LineCount");
        var result = (int)propertyInfo.GetValue(tb);
        return result;
    }

    private static PropertyInfo GetPrivatePropertyInfo(Type type, string propertyName)
    {
        var props = type.GetProperties(BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic);
        return props.FirstOrDefault(propInfo => propInfo.Name == propertyName);
    }
}
0 голосов
/ 22 декабря 2009

Простым способом является свойство LineCount. Также у вас есть метод GetLastVisibleLineIndex, который позволяет узнать, сколько строк может отображать текстовое поле (без полос прокрутки).

Если вы хотите знать, когда добавляется строка, вы можете услышать о событии TextChanged и спросить о свойстве LineCount (вам нужно будет сохранить las LineCount в переменной для выполнения сравнения).

...