Как настроить элемент управления TextBox для автоматического изменения размера по вертикали, когда текст больше не помещается на одной строке? - PullRequest
12 голосов
/ 11 марта 2011

Как настроить TextBox элемент управления для автоматического изменения размера по вертикали, когда текст больше не помещается на одной строке?

Например, в следующем XAML:

<DockPanel LastChildFill="True" Margin="0,0,0,0">
  <Border Name="dataGridHeader" 
    DataContext="{Binding Descriptor.Filter}"
    DockPanel.Dock="Top"                         
    BorderThickness="1"
    Style="{StaticResource ChamelionBorder}">
  <Border
    Padding="5"
    BorderThickness="1,1,0,0"                            
    BorderBrush="{DynamicResource {ComponentResourceKey TypeInTargetAssembly=dc:NavigationPane, 
    ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleBorder}}}">
    <StackPanel Orientation="Horizontal">
      <TextBlock                                
        Name="DataGridTitle"                                                                                                
        FontSize="14"
        FontWeight="Bold"                                    
        Foreground="{DynamicResource {ComponentResourceKey 
        TypeInTargetAssembly=dc:NavigationPane, 
        ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleForeground}}}"/>
      <StackPanel Margin="5,0"  Orientation="Horizontal" 
              Visibility="{Binding IsFilterEnabled, FallbackValue=Collapsed, Mode=OneWay, Converter={StaticResource BooleanToVisibility}}"
              IsEnabled="{Binding IsFilterEnabled, FallbackValue=false}"  >                                    
          <TextBlock  />                                                                
          <TextBox    
            Name="VerticallyExpandMe"
            Padding="0, 0, 0, 0"  
            Margin="10,2,10,-1"                                                                                                                                                                                                                     
            AcceptsReturn="True"
            VerticalAlignment="Center"                                    
            Text="{Binding QueryString}"
            Foreground="{DynamicResource {ComponentResourceKey 
            TypeInTargetAssembly=dc:NavigationPane, 
            ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleForeground}}}">
          </TextBox>
        </StackPanel>                               
    </StackPanel>
  </Border>              
  </Border>
</DockPanel>

TextBox элемент управления с именем "VerticalExpandMe" должен автоматически расширяться по вертикали, когда связанный с ним текст не помещается на одной строке.Если для AcceptsReturn установлено значение true, TextBox расширяется по вертикали, если я нажимаю Enter внутри него, но я хочу, чтобы это делалось автоматически.

Ответы [ 5 ]

43 голосов
/ 11 марта 2011

Хотя предложение Андре Лууса в основном верно, оно на самом деле не сработает, потому что ваш макет будет препятствовать переносу текста.Я объясню, почему.

По сути, проблема заключается в следующем: перенос текста делает все что угодно, когда ширина элемента ограничена, но ваша TextBox имеет неограниченную ширину, потому что это потомок горизонтального StackPanel.(Ну, две горизонтальные панели стека. Возможно, больше, в зависимости от контекста, из которого вы взяли пример.) Поскольку ширина не ограничена, TextBox не имеет представления, когда он должен начинать перенос, и поэтому никогда не будет переносить., даже если вы включите обтекание.Вам нужно сделать две вещи: ограничить его ширину и включить перенос.

Вот более подробное объяснение.

Ваш пример содержит много деталей, не имеющих отношения к проблеме.Вот версия, которую я несколько урезал, чтобы было легче объяснить, что не так:

<StackPanel Orientation="Horizontal">
    <TextBlock Name="DataGridTitle" />
    <StackPanel
        Margin="5,0"
        Orientation="Horizontal"
        >
        <TextBlock />
        <TextBox
            Name="VerticallyExpandMe"
            Margin="10,2,10,-1"
            AcceptsReturn="True"
            VerticalAlignment="Center"
            Text="{Binding QueryString}"
            >
        </TextBox>
    </StackPanel>
</StackPanel>

Итак, я удалил содержащийся в нем элемент DockPanel и два вложенных элемента Border внутри него, потому чтоони не являются частью проблемы и не имеют отношения к решению.Итак, я начинаю с пары вложенных StackPanel элементов в вашем примере.И я также удалил большинство атрибутов, потому что большинство из них также не имеют отношения к макету.

Это выглядит немного странно - наличие двух вложенных горизонтальных панелей стека, как это выглядит избыточно, но на самом деле делаетсмысл в вашем оригинале, если вам нужно сделать вложенный один видимым или невидимым во время выполнения.Но это облегчает выявление проблемы.

(Пустой тег TextBlock тоже странный, но это именно так, как это выглядит в вашем оригинале. Похоже, что он не делает ничего полезного.)

И вот в чем проблема: ваш TextBox находится внутри некоторых горизонтальных StackPanel элементов, что означает, что его ширина не ограничена - вы непреднамеренно сообщили текстовому полю, что оно свободно увеличивается до любой ширины, независимо от того, насколькопространство фактически доступно.

A StackPanel всегда будет выполнять макет, который не ограничен в направлении укладки.Поэтому, когда речь заходит о том, что TextBox, он пройдет в горизонтальном размере double.PositiveInfinity до TextBox.Так что TextBox всегда будет думать, что у него больше места, чем нужно.Более того, когда ребенок StackPanel запрашивает больше места, чем на самом деле доступно, StackPanel лжет и делает вид, что дает ему столько места, но затем обрезает его.

(Это ценавы платите за предельную простоту StackPanel - она ​​проста до такой степени, что она кость с головой, потому что она с удовольствием создаст макеты, которые на самом деле не подходят. Вы должны использовать StackPanel, только если у вас действительно есть неограниченное пространствопотому что вы находитесь внутри ScrollViewer, или вы уверены, что у вас достаточно мало предметов, у которых не останется свободного места, или если вас не волнует, что предметы выходят с конца панели, когда онистановится слишком большим, и вы не хотите, чтобы система макетов пыталась сделать что-то более умное, чем просто обрезка содержимого.)

Так что включение переноса текста здесь не поможет, потому что StackPanel всегда будетпредставьте, что для текста более чем достаточно места.

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

Вот простой пример, который примерно соответствует тому, что вы хотите:

<Grid VerticalAlignment="Top">
    <DockPanel>
        <TextBlock
            x:Name="DataGridTitle"
            VerticalAlignment="Top"
            DockPanel.Dock="Left"
            />
        <TextBox
            Name="VerticallyExpandMe"
            AcceptsReturn="True"
            TextWrapping="Wrap"
            Text="{Binding QueryString}"
            >
        </TextBox>
    </DockPanel>
</Grid>

Если вы создаете совершенно новое приложение WPF и вставляете его в качестве содержимого главного окна, вы должны найти, что оно делает то, что вам нужно - TextBox начинается на одну строку выше, заполняет доступную ширину, и если вы набираететекст будет увеличиваться по одной строке по мере добавления текста.

Конечно, поведение макета всегда чувствительно к контексту, поэтому может быть недостаточно просто выбросить это в середину существующего приложения.Это будет работать, если вставлено в пространство фиксированного размера (например, как тело окна), но не будет работать правильно, если вы вставите его в контекст, где ширина не ограничена.(Например, внутри ScrollViewer или внутри горизонтального StackPanel.)

Так что, если это не сработает для вас, это будет из-за других вещей, которые не так в других местах вашего макета - возможно, покабольше StackPanel элементов в другом месте.Судя по вашему примеру, вероятно, стоит потратить некоторое время на то, чтобы подумать о том, что вам действительно нужно в макете, и упростить его - наличие отрицательных полей и элементы, которые, по-видимому, не делают ничего подобного пустому TextBlock, обычносвидетельствует о чрезмерно сложной компоновке.А излишняя сложность макета значительно усложняет достижение искомых эффектов.

6 голосов
/ 02 мая 2012

В качестве альтернативы, вы можете ограничить TextBlock Width, привязав его к ActualWidth родителя, например:

<TextBlock Width="{Binding ElementName=*ParentElement*, Path=ActualWidth}" 
           Height="Auto" />

Это также приведет к автоматическому изменению высоты.

2 голосов
/ 06 февраля 2014

Используйте MaxWidth и TextWrapping="WrapWithOverflow".

0 голосов
/ 15 октября 2013

Вы можете использовать этот класс, который расширяет TextBlock.Это делает автоматическое сжатие и учитывает MaxHeight / MaxWidth:

public class TextBlockAutoShrink : TextBlock
    {
        private double _defaultMargin = 6;
        private Typeface _typeface;

        static TextBlockAutoShrink()
        {
            TextBlock.TextProperty.OverrideMetadata(typeof(TextBlockAutoShrink), new FrameworkPropertyMetadata(new PropertyChangedCallback(TextPropertyChanged)));
        }

        public TextBlockAutoShrink() : base() 
        {
            _typeface = new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch, this.FontFamily);
            base.DataContextChanged += new DependencyPropertyChangedEventHandler(TextBlockAutoShrink_DataContextChanged);
        }

        private static void TextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var t = sender as TextBlockAutoShrink;
            if (t != null)
            {
                t.FitSize();
            }
        }

        void TextBlockAutoShrink_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            FitSize();
        }

        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            FitSize();

            base.OnRenderSizeChanged(sizeInfo);
        }


        private void FitSize()
        {
            FrameworkElement parent = this.Parent as FrameworkElement;
            if (parent != null)
            {
                var targetWidthSize = this.FontSize;
                var targetHeightSize = this.FontSize;

                var maxWidth = double.IsInfinity(this.MaxWidth) ? parent.ActualWidth : this.MaxWidth;
                var maxHeight = double.IsInfinity(this.MaxHeight) ? parent.ActualHeight : this.MaxHeight;

                if (this.ActualWidth > maxWidth)
                {
                    targetWidthSize = (double)(this.FontSize * (maxWidth / (this.ActualWidth + _defaultMargin)));
                }

                if (this.ActualHeight > maxHeight)
                {
                    var ratio = maxHeight / (this.ActualHeight);

                    // Normalize due to Height miscalculation. We do it step by step repeatedly until the requested height is reached. Once the fontsize is changed, this event is re-raised
                    // And the ActualHeight is lowered a bit more until it doesnt enter the enclosing If block.
                    ratio = (1 - ratio > 0.04) ? Math.Sqrt(ratio) : ratio;

                    targetHeightSize = (double)(this.FontSize *  ratio);
                }

                this.FontSize = Math.Min(targetWidthSize, targetHeightSize);
            }
        }
    }
0 голосов
/ 18 декабря 2011

Я использую другой простой подход, который позволяет мне не изменять макет документа.

Основная идея заключается в том, чтобы не устанавливать элемент управления Width до его изменения.Для TextBox es я обрабатываю событие SizeChanged:

<TextBox TextWrapping="Wrap" SizeChanged="TextBox_SizeChanged" />

private void TextBox_SizeChanged(object sender, SizeChangedEventArgs e)
{
    FrameworkElement box = (FrameworkElement)sender;
    if (e.PreviousSize.Width == 0 || box.Width < e.PreviousSize.Width)
        return;
    box.Width = e.PreviousSize.Width;
}
...