Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Visible line count of a TextBlock

If you set TextWrapping to "Wrap", a WPF TextBlock can have several lines of text. Is there a "clean" way to get the number of lines of text? I considered looking at the desired height and dividing it by an estimated height of each line. However, that seems quite dirty. Is there a better way?

like image 681
tom7 Avatar asked Jul 09 '09 19:07

tom7


People also ask

What is TextBlock?

Text block is the primary control for displaying read-only text in apps. You can use it to display single-line or multi-line text, inline hyperlinks, and text with formatting like bold, italic, or underlined.

Is TextBlock editable?

TextBlocks can be edited by users using the TextEditingTool. The HTMLInfo that a given TextBlock uses as its text editor can be customized by setting the textEditor property.

What is TextBlock WPF?

The TextBlock control provides flexible text support for UI scenarios that do not require more than one paragraph of text. It supports a number of properties that enable precise control of presentation, such as FontFamily, FontSize, FontWeight, TextEffects, and TextWrapping.


1 Answers

One thing about WPF that's very nice is that all of the controls are very lookless. Because of this, we can make use of TextBox, which has a LineCount property (Why it's not a DependencyProperty or why TextBlock doesn't also have it I do not know). With the TextBox, we can simply re-template it so it behaves and looks more like a TextBlock. In our custom Style/Template we're going to set IsEnabled to False, and just create a basic re-templating of the control so that the disabled look is no longer present. We can also bind any properties we want to maintain, like Background, through the use of 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>

Now, that will take care of making our TextBox look and behave like a TextBlock, but how do we get the line count?

Well, if we want to access it directly in the code behind then we can register to the TextBox's SizeChanged Event.

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));
}

However, since I tend to need to use properties like that in places other then the current window, and/or am using MVVM and don't want to take that approach, then we can create some AttachedProperties to handle the retrieval and setting of the LineCount. We're going to use the AttachedProperties to do the same thing, but now we'll be able to use it with any TextBox anywhere, and bind to it through that TextBox instead of the Window's 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
}

Now, it's simple to find the 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>
like image 169
rmoore Avatar answered Sep 29 '22 12:09

rmoore