2009-07-09 17 views
8

Po ustawieniu opcji TextWrapping na "Wrap", blokowanie tekstu WPF może mieć kilka wierszy tekstu. Czy istnieje "czysty" sposób na uzyskanie liczby linii tekstu? Rozważałem, patrząc na pożądaną wysokość i dzieląc ją przez szacunkową wysokość każdej linii. Jednak wydaje się to dość brudne. Czy istnieje lepszy sposób?Widoczna liczba wierszy obiektu TextBlock

Odpowiedz

8

Jedną z cech WPF jest to, że wszystkie elementy sterujące są bardzo nieskazitelne. Z tego powodu możemy skorzystać z TextBox, która ma właściwość LineCount (Dlaczego to nie jest DependencyProperty lub dlaczego TextBlock również go nie ma, nie wiem). Za pomocą TextBox możemy po prostu zmienić szablon, aby zachowywał się i wyglądał bardziej jak TextBlock. W naszym niestandardowym stylu/szablonie ustawimy IsEnabled na False i po prostu utworzymy podstawową zmianę struktury kontrolki, aby wyłączony wygląd nie był już obecny. Możemy również powiązać dowolne właściwości, które chcemy zachować, takie jak tło, za pomocą 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> 

teraz, że zadba o to, ze nasz TextBox wyglądają i zachowują się jak TextBlock, ale w jaki sposób dostać się liczyć linia?

Cóż, jeśli chcemy uzyskać do niego dostęp bezpośrednio w kodzie, możemy zarejestrować się w wydaniu SizeChanged w 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)); 
} 

Jednakże, ponieważ staram się trzeba użyć właściwości takiego w miejscach innych ówczesnych bieżącego okna i/lub używam MVVM i nie chcą podjąć takie podejście, to możemy stworzyć kilka AttachedProperties do obsłużyć pobieranie i ustawienie LineCount. Zamierzamy użyć AttachedProperties, aby zrobić to samo, ale teraz będziemy mogli użyć go z dowolnym tekstem TextBox w dowolnym miejscu i powiązać go za pomocą tego TextBox zamiast DataContext okna.

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 
} 

teraz, to proste, aby znaleźć 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> 
+0

To jest świetne. Jednak TextBox są bardziej ograniczone niż TextBlock, ponieważ mają jednolitą rodzinę czcionek/rozmiar czcionki. W rezultacie łatwo obliczyć liczbę linii. Z drugiej strony TextBlocks mogą mieć różne inline o różnych wysokościach. To sprawia, że ​​rzeczy nieco trudniejsze. – tom7

-2

Prostym sposobem jest własnością LineCount. Masz również metodę o nazwie GetLastVisibleLineIndex, która informuje, ile linii może wyświetlać pole tekstowe (bez pasków przewijania).

Jeśli chcesz wiedzieć, kiedy linia jest dodawana, możesz usłyszeć w zdarzeniu TextChanged i zapytać o właściwość LineCount (aby porównać, musisz zachować wartość LineCount dla lasów).

+0

TextBlock nie ma właściwości LineCount. To wyłącznie domena TextBox. –

+0

Znalazłem to przydatne. dobra informacja, zła odpowiedź. – mdw7326

3
// 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; 
       } 
      } 
     } 
+0

Pytanie dotyczyło TextBlock a nie TextBox. – jHilscher

1

Widziałem, że ta kwestia jest już 7 lat, ale ja po prostu przyszedł z roztworem:

TextBlock dysponują właściwość o nazwie LineCount. Stworzyłem metodę rozszerzenia, aby odczytać tę wartość:

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