开发者

Prevent a TextBox from horizontal expanding in WPF

I have the following style defined in my App.xaml

<Style x:Key="textBoxMultiline" TargetType="{x:Type TextBox}" >
    <Setter Property="VerticalScrollBarVisibility" Value="Auto" />
    <Setter Property="HorizontalScrollBarVisibility" Value="Hidden" />
    <Setter Property="MinHeight" Value="50" />
    <Setter Property="TextWrapping" Value="Wrap" />
</Style>

And throughout the solution we're using it on every text box that needs a brief text.

<TextBox x:Name="textBoxDescription" Grid.Row="2" Grid.Column="1" Style="{DynamicResource 开发者_运维问答textBoxMultiline}" />

Everything works great, but then the client complains about some fields were corped on older monitors with lower resolutions, so I placed a ScrollViewer on one of the higher visual tree nodes to prevent the corping.

<ScrollViewer Height="Auto" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
   ...
</ScrollViewer>

Strangely, the TextBoxes with the above style start expanding to the right instead of wrapping the text.

Is there a way to prevent this without removing the ScrollViewer?


If you don't want to hard code the width then you can go for element binding the width of the parent item.

Here I am binding TextBox MaxWidth with ScrollViewer actual width. You also have to make sure that the ColumnDefinition width should be set to "*" not to "Auto". If you set it to Auto it will neglect the ScrollViewer width and keep on expanding the width of ScrollViewer and TextBox. I think you fall in this case...

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"></ColumnDefinition>
        <ColumnDefinition Width="*"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <ScrollViewer HorizontalScrollBarVisibility="Auto" Name="scv">
        <TextBox Height="30" TextWrapping="Wrap" MaxWidth="{Binding ElementName=scv, Path=ActualWidth}"></TextBox>
    </ScrollViewer>
</Grid>


You must define a MaxWidth for the TextBox, otherwise there's no limit because the ScrollViewer.


The solution provided from @bathineni helped me solve my problem. Here is what worked for me:

<Grid >
    <Grid.ColumnDefinitions>
    <ColumnDefinition Width="50"/>
    <ColumnDefinition  Width="*"/>
</Grid.ColumnDefinitions>
    <Button Grid.Column="0" Width="30" Height="23" Margin="10,5" Content="..."/>
    <ScrollViewer  Grid.Column="1" HorizontalScrollBarVisibility="Disabled" verticalScrollBarVisibility="Disabled" Name="scv">
         <TextBox Height="25" Text="Insert here long text" MaxWidth="{Binding ElementName=scv, Path=ActualWidth}" HorizontalAlignment="Stretch" />
    </ScrollViewer>
</Grid>


I tried the aforementioned examples and they didn't work so, I solved the problem myself. There are two ways of solving this issue:

The first solution is implemented in XAML using data bindings. I advice you not to bind the control by itself. The XAML solution is implemented by binding a control with the desired ActualWidth and ActualHeight proprieties to the textbox MaxHeight and MaxWidth proprieties.

<TextBlock x:Name="PasswordText" Margin="0,0,0,20" FontFamily="Bahnschrift SemiBold Condensed" Text="PASSWORD" FontSize="20"> 

 
<TextBox x:Name="PasswordTextBox" MaxWidth="{Binding ElementName=PasswordText, Path=ActualWidth}" MaxHeight="{Binding ElementName=PasswordText, Path=ActualHeight}">

The next solution is implemented by generating a Loaded event in XAML, creating it in the C# code and then setting within the Loaded event the MaxWidth and MaxHeight proprieties of the textbox as the textbox ActualWidth and ActualHeight proprieties.

//  It doesn't have a problem like in XAML if you pass the textbox its own   
//  ActualWidth and ActualHeight to the MaxWidth and MaxHeight proprieties. 


        private void Log_In_Page_Loaded(object sender, RoutedEventArgs e)
        {
            UsernameTextBox.MaxHeight = UsernameTextBox.ActualHeight;
            UsernameTextBox.MaxWidth = UsernameTextBox.ActualWidth;
        }

Choose the one that suits your design better, but I think, in my opinion, that this is the most effective, simple, stable and effective way of solving this problem.


Works for me. If you want scrollbars to appear in the textbox, you may add

HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"

to the TextBox


You must set MaxWidth of the Container Control

<Grid x:Name="RootGrid" Margin="6,6,8,8" Width="500" MaxWidth="500">
<ScrollViewer ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto">
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <GroupBox Name="contentGroup" Header="Content" Grid.Row="0">
      <TextBox Name="content"/>
    </GroupBox>
  </Grid>
</ScrollViewer>


I ran into this problem when I needed my TextBox to stretch along with its auto-sized Grid column when it got resized, meaning MaxWidth wouldn't work, but still needed to prevent the TextBox from stretching along with its contents.

What I ended up doing was linking this event handler to SizeChanged:

private void TextBox_SizeChanged(object sender, SizeChangedEventArgs e) {
    TextBox textBox = (TextBox)sender;
    if (textBox.CanUndo && e.NewSize.Width > e.PreviousSize.Width) {
        textBox.Width = e.PreviousSize.Width;
    }
}

Writing text to a textbox is something that the user can Undo, whereas other actions that can cause resizing (initial drawing of the element, stretching of parent container, etc) aren't Undoable from within the textbox. Thus, by checking CanUndo we can determine whether SizeChanged was triggered by writing text or something else.

The e.NewSize.Width > e.PreviousSize.Width check is necessary because without it the SizeChanged event will infinitely be called from within itself, because to revert the stretching we need to change the size back to the original, which would itself trigger the event.

It's a little hacky but I haven't run into any issues yet.


I am not sure why but I could not get the ScrollViewer solution to work. I needed to have a TextBox with a fixed initial width to implement a numeric up/down control - in this control the TextBox was shrinking and growing independent of the input which looks very annoying if the UI changes as you type.

So, I found the below solution using 2 textboxes to work for me. The first textbox is the textbox displayed for the user to type their input and the 2nd textbox is initialized through a dependency property (DisplayLength) and the converter shown further below.

Binding the MaxWidth property of the 1st TextBox to the Width property of the 2nd TextBox fixes the size such that users can type what they want but the displayed width of the textbox will not change even if there is more UI space available.

<TextBox x:Name="PART_TextBox"
    Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value}"
    Margin="0,0,1,0"
    TextAlignment="Right"
    AcceptsReturn="False"
    SpellCheck.IsEnabled="False"
    HorizontalContentAlignment="Stretch"
    VerticalContentAlignment="Center"
    HorizontalAlignment="Stretch"
    VerticalAlignment="Stretch"
    MaxWidth="{Binding ElementName=TBMeasure, Path=ActualWidth}"
    />

<!-- Hidden measuring textbox ensures reservation of enough UI space
     according to DisplayLength dependency property
-->
<TextBox x:Name="TBMeasure"
    Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DisplayLength, Converter={StaticResource ByteToPlaceHolderStringConverter}}"
    Margin="0,0,1,0"    
    TextAlignment="Right"
    AcceptsReturn="False"
    SpellCheck.IsEnabled="False"
    HorizontalContentAlignment="Right"
    VerticalContentAlignment="Center"
    HorizontalAlignment="Stretch"
    VerticalAlignment="Stretch"
    Visibility="Hidden"/>



// Converter
[ValueConversion(typeof(byte), typeof(string))]
public sealed class ByteToPlaceHolderStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if ((value is byte) == false)
            return Binding.DoNothing;

        byte byteVal = (byte)value;

        string retString = string.Empty;
        for (int i = 0; i < byteVal; i++)
            retString = retString + "X";

        return retString;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return Binding.DoNothing;
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜