Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF: How to make a textbox dynamically sized yet prevent autosizing?

I know there are a lot of questions in regard to the auto sizing of text boxes in WPF, but I couldn't find a solution for the following problem.

Consider this simple window:

<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>

This implements these two constraints I need:

  1. This setup will make the second text box sized dynamically so that it uses the remaining window space.
  2. If the window becomes too small for the required minimum size of the ScrollViewer's contents, the ScrollViewer shows a scrollbar.

However, when you type too much text in the second text box, the ScrollViewer shows a scrollbar, instead of the TextBox. I'd like to stop the text box from increasing its height beyond the space given by the parent Grid originally. I can't use MaxHeight in this case because there is no suitable ActualHeight to bind to (as far as I can see).

Any suggestions on how to solve this (preferably without code-behind)?

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

In HTML what I want would translate to this:

<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>
like image 758
floele Avatar asked Jan 21 '14 10:01

floele


3 Answers

Scrollable-expandable-controls problem.

Scrollable-expandable-controls : are controls that can stretch as its content grows and will display scrollbars when their size is restricted.

Problem appears when they are located inside another scrollable control. Child scrollable-expandable-controls will keep expanding and will count on the outer scrollable control's scrollbars.

if you give it a maximum width or height problem will be resolved but you will need to know the size ahead and you don't have this privilege if you want a dynamic app that works well with all different screen sizes.

in order to achieve required behavior , we need a panel in between to allow its children (scrollable-expandable-control) to grow asking them to give the minimum required size and then give them the maxiumum size the parent provides without displaying scrollbars , currently there is no panel like this.

Here is a one that I developed to provide this functionality:

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

and then inside your xaml incapsulate your scrollable-expandable-control in it.

        <l:LimitChild
            Grid.Row="1">
            <TextBox
                VerticalScrollBarVisibility="Auto"
                HorizontalScrollBarVisibility="Auto"
                MinHeight="200"
                AcceptsReturn="True">Test</TextBox>
        </l:LimitChild>
like image 107
Ashi Avatar answered Nov 15 '22 05:11

Ashi


Try this:

<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>

This should exactly meet your requirements.

like image 43
Torben Schramme Avatar answered Nov 15 '22 05:11

Torben Schramme


Actually you can bind to ActualHeight by accessing ancestor using RelativeSource binding:

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


Update:

If you what that scroll bar should be shown only for second TextBox - put ScrollViewer only for it:

    <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>        
like image 39
Anatolii Gabuza Avatar answered Nov 15 '22 04:11

Anatolii Gabuza