WPF: как сделать текстовое поле динамического размера, но предотвратить авторазмер?

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

рассмотрим это простое окно:

<Window x:Class="TestVisualBrush.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="470" Width="608">
<ScrollViewer>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <TextBox>Test</TextBox>
        <TextBox MinHeight="100" Grid.Row="1" AcceptsReturn="True" >Test</TextBox>
    </Grid>
</ScrollViewer>
</Window>

это реализует эти два ограничения, которые мне нужны:

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

однако, когда вы вводите слишком много текста во втором текстовом поле, то ScrollViewer показывает полосу прокрутки, вместо TextBox. Я хотел бы остановить текстовое поле от увеличения его высоты за пределами пространства, заданного родителем Grid изначально. Я не могу использовать MaxHeight в этом случае, потому что нет подходящего ActualHeight привязать к (Насколько я вижу).

любые предложения о том, как это решить (желательно без кодом)?

Note that the root ScrollViewer should still scroll if its contents are too large for the window.

в HTML, что я хочу перевести на это:

<table height="100%">
 <tr>
    <td><input type="text"></td>
 </tr>
 <tr height="100%"> 
    <td>
          <!-- Uses as much space as it gets, but scrolls if text inside
               gets too large. Makes outer window scroll if too small
               for min-height and other controls in table. -->
      <textarea style="height:100%;min-height:100px"></textarea>
    </td>
  </tr>
</table>

6 ответов


прокручиваемый-расширяемый-проблема управления.

Scrollable-expandable-controls: это элементы управления, которые могут растягиваться по мере роста содержимого и отображать полосы прокрутки, когда их размер ограничен.

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

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

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

вот один, который я разработал, чтобы обеспечить эту функциональность:

    class LimitChild : System.Windows.Controls.Panel
    {
        public LimitChild()
        {
        }

        protected override Size MeasureOverride(System.Windows.Size availableSize)
        {
            System.Diagnostics.Debug.Assert(InternalChildren.Count == 1);
            System.Windows.UIElement child = InternalChildren[0];

            Size panelDesiredSize = new Size();
            // panelDesiredSize.Width = availableSize.Width;
            panelDesiredSize.Width = (double)child.GetValue(FrameworkElement.MinWidthProperty);
            panelDesiredSize.Height = (double)child.GetValue(FrameworkElement.MinHeightProperty);

            child.Measure(panelDesiredSize);

            // IMPORTANT: do not allow PositiveInfinity to be returned, that will raise an exception in the caller! 
            // PositiveInfinity might be an availableSize input; this means that the parent does not care about sizing 
            return panelDesiredSize;
        }

        protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
        {
            System.Windows.UIElement child = InternalChildren[0];

            child.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
            if (finalSize.Width > child.RenderSize.Width)
                finalSize.Width = child.RenderSize.Width;
            if (finalSize.Height > child.RenderSize.Height)
                finalSize.Height = child.RenderSize.Height;

            return finalSize; // Returns the final Arranged size
        }
    }

а потом внутри XAML-код инкапсулировать прокручиваемом-расширяемый-контроль.

        <l:LimitChild
            Grid.Row="1">
            <TextBox
                VerticalScrollBarVisibility="Auto"
                HorizontalScrollBarVisibility="Auto"
                MinHeight="200"
                AcceptsReturn="True">Test</TextBox>
        </l:LimitChild>

попробуйте это:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBox />
        <TextBox AcceptsReturn="True" Grid.Row="1" VerticalScrollBarVisibility="Auto" />
    </Grid>
</Window>

Это должно точно соответствовать вашим требованиям.


попробуйте это :

<Grid>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <TextBox Height="50"/>

        <TextBox Grid.Row="1" AcceptsReturn="True" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" />

    </Grid>

и вы сделали :)


где-то вдоль линии вы должны установить максимальную высоту текстового поля прокрутки включен. Обычно * делает это, но поскольку он завернут в Средство просмотра прокрутки, содержащая сетка не имеет эффективного максимума. Вы можете использовать MultiValueConverter как это

    public class MaxHeightConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        double result = (double)values[0];
        for (int n = 1; n < values.Length; ++n)
        {
            result = result - (double)values[n];
        }
        return result;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

тогда ваш XAML будет выглядеть так

<Window x:Class="wpf_Hacks.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:wpf_Hacks"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <local:MaxHeightConverter x:Key="maxHeightConverter"></local:MaxHeightConverter>
</Window.Resources>
<ScrollViewer>
    <Grid x:Name="rootGrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <TextBox x:Name="headerTextBox" Height="50" Text="Textbox 1"/>

        <TextBox MinHeight="100" Grid.Row="1" AcceptsReturn="True" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" Text="Textbox 2">
            <TextBox.MaxHeight>
                <MultiBinding Converter="{StaticResource maxHeightConverter}">
                    <Binding ElementName="rootGrid" Path="ActualHeight"></Binding>
                    <Binding ElementName="headerTextBox" Path="ActualHeight"></Binding>
                </MultiBinding>
            </TextBox.MaxHeight>
        </TextBox>

    </Grid>
</ScrollViewer>

в основном Multi value converter принимает первое связанное значение в качестве общей высоты, доступной, а затем уменьшает это на высоты других связанных элементов, оставляя оставшееся пространство для окончательного текстового поля

надеюсь, что это помогает


на самом деле вы можете привязать к ActualHeight заходя предка, используя RelativeSource обязательные:

Height="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}},
                 Path=RowDefinitions[1].ActualHeight}"


обновление:

если вы, что эта полоса прокрутки должна отображаться только для второй TextBox - ставим ScrollViewer только для этого:

    <Grid>
        <Grid.RowDefinitions>
           <RowDefinition Height="Auto" />
           <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <TextBox>Test</TextBox>
        <ScrollViewer Grid.Row="1">                
            <TextBox MinHeight="100" AcceptsReturn="True" >Test</TextBox>                
        </ScrollViewer>
    </Grid>        

Я придумал что-то вроде этого, однако я все еще тестирую, просто нужно знать, если это то, что Вы себе представляли?

<Window x:Class="testWpf.WindowTextBoxInnerScroll"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="200" Width="608" Name="MyWindow" Loaded="MyWindow_Loaded">

<ScrollViewer VerticalScrollBarVisibility="Auto" >
<Grid name="MyGrid">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

        <TextBox>Test</TextBox>
        <TextBox Name="TB2" Grid.Row="1" MinHeight="100" TextWrapping="Wrap" 
                 VerticalScrollBarVisibility="Auto" AcceptsReturn="True" >Test</TextBox>
</Grid>
</ScrollViewer>

код

private void MyWindow_Loaded(object sender, RoutedEventArgs e)
{
  if (this.Height > MyGrid.RowDefinitions[0].ActualHeight + MyGrid.RowDefinitions[1].ActualHeight)
  {
    TB2.Height = MyGrid.RowDefinitions[1].ActualHeight;
  }
  else
  {
    TB2.Height = TB2.MinHeight;
  }
}

обновление

Если это не то, что вы хотите, то я не уверен, каковы ваши ожидания на этот вопрос.

надеюсь, что это помогает.

обновление 2

я обновил свой XAML и код позади, это работает для мне. (Думаю).