Within a Grid, how to set a textblock's width - wpf

I have a Grid, there are two columns, here is the definition.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Name="nText" Text="{Binding Name}" Grid.Column="0" />
<TextBlock Name="vText" Text="{Binding Value}" Grid.Column="1" HorizontalAlignment="Left" TextWrapping="Wrap"/>
</Grid>
My question is, for the vText, when the Text is too long, the text can not be wrapped, no matter the columndefinition width is auto or *. But if I set a definited value to the column 2 's width or to the vText 's width, the text will be wrapped.
How can I set the textblock's width so that the text content can be wrapped?
I have tried to bind to columnDefinition's width/ActualWidth, but also failed.
Great thanks.

To elaborate on BalamBalam's answer, changing
<ColumnDefinition Width="auto"></ColumnDefinition>
to
<ColumnDefinition Width="*"></ColumnDefinition>
will do what you want because "auto" mode expects to infer its size based on the child elements. In this case, your TextBlock doesn't have a Width defined, so there's no way for the ColumnDefinition to infer the width.
Because neither the ColumnDefinition nor the TextBlock have a width defined, the TextBlock's width will default to infinite, and the word wrap algorithm will never clip the text.
By contrast, "*" means to fill remaining space, which will either be defined by the grid (if you set a width on it), or one of its parents. Worst case, the "*" will be able to find a value at the top level, because a window always has a width/height set.
Hopefully this sheds a bit more light on how the layout engine does its magic with sizes!

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Name="nText" Text="nText"/>
<TextBlock Grid.Column="1" Grid.Row="0" Name="vText" TextWrapping="Wrap">
vText vTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvText
vTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvText
vTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvTextvText
</TextBlock>
</Grid>

Related

Width of Textbox, which placed in grid column, extends when inserting long text

When I put a Textbox in a grid column like below
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="115"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="90"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="hello"/>
<TextBox Grid.Column="1" />
<Button Grid.Column="2" Content="push me"/>
</Grid>
</StackPanel>
I get proper result, i.e. textbox width is get from parent grid
But when I type a long text, the textbox starts exceeding its column and it stops extending after several extra letters
To .Net 4.6.2, I get same result but changing to .Net 4.7.2 the problem is solved i.e. the textbox width is not changing.
My software is compiled .Net 4.0, is there a solution to solve this for lower .net than 4.7.2?
Tried Pavel's first idea: removing stackpanel and adding another grid row in, still not working in .net 4.6.2
Tried Pavel's second idea: making the width of first column "Auto" instead of "1*". This works, the textbox doesn't extend (.net 4.6.2), however I really wanted the first and second column be responsive to resize.
You can solve this be removing the StackPanel and adding RowDefinition to Grid. You can also set TextWrapping="Wrap" for TextBox for wrapping a long text
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="90"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Text="hello" MinWidth="115"/>
<TextBox Grid.Column="1" TextWrapping="Wrap"/>
<Button Grid.Column="2" Content="push me"/>
</Grid>
StackPanel Y dimension is constrained to content, it will be automatically expanded to its content, more details can be found at panels overview
To me this is a bug that Textbox text size can change its parent width (grid width). This is happening in VS2019 .Net 4->4.6.2 but not in and after .Net 4.7.2.
For anybody faces this problem, I found below workaround by defining an invisible grid which contains textblocks. I get the widths of columns and give them to controls placed in a StackPanel.
<some container>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" MinWidth="115"/>
<ColumnDefinition Width="3*" />
<ColumnDefinition MinWidth="90"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0"/>
</Grid.RowDefinitions>
<TextBlock x:Name="C0" Grid.Column="0" />
<TextBlock x:Name="C1" Grid.Column="1" Margin="5"/>
<TextBlock x:Name="C2" Grid.Column="2" />
</Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Width="{Binding ElementName=C0, Path=ActualWidth }" Text="hello" />
<TextBox Width="{Binding ElementName=C1, Path=ActualWidth }" Margin="5" />
<Button Width="{Binding ElementName=C2, Path=ActualWidth }" Content="push me"/>
</StackPanel>
</some container>
The margin should be the same for column in grid and control in StackPanel (see second column).

WPF Gridsplitter does not work with UseLayoutRounding

If the wpf application has set UseLayoutRounding to true for the window then a gridsplitter will no longer work for some window widths (it will be stuck at its original position), such as the example below.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="200" Width="401"
UseLayoutRounding="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0">
long string that does not fit within the textblock - long string that does not fit within the textblock
</TextBlock>
<GridSplitter Grid.Column="1" HorizontalAlignment="Center" Width="50" Background="LightBlue" />
<TextBlock Grid.Column="2">
long string that does not fit within the textblock - long string that does not fit within the textblock
</TextBlock>
</Grid>
</Window>
Notice that for the example to generate the error the window Width must be 401, the text in the textboxes must be longer than the textbox and UseLayoutRounding must be true.
Anyone that knows how to avoid this or have any workaround? I don't want to set UseLayoutRounding to false since it causes rendering artifacts in my application.
Edit:
For others with the same problem, I found this user made component that solves the problem: http://blog.onedevjob.com/2011/12/11/fixing-wpf-gridsplitter/ Would still be nice if it could be solved with the default wpf components though.
Interesting problem. One obvious workaround is to set TextWrapping="WrapWithOverflow" for at least one of TextBlock elements but I'm sure this was not your intention.
Another workaround is to not to give a splitter it's own grid column and instead place it in the second, last column. Seems to do the trick:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0">
long string that does not fit within the textblock - long string that does not fit within the textblock
</TextBlock>
<GridSplitter Grid.Column="1" Margin="-25,0,0,0" HorizontalAlignment="Left" Width="50" Background="LightBlue" />
<TextBlock Grid.Column="1" Margin="25,0,0,0">
long string that does not fit within the textblock - long string that does not fit within the textblock
</TextBlock>
</Grid>

How to layout controls and make it resizable

I would like to place the controls as listed below in WPF. Please given an example to do the same.
<Grid x:Name="ContentRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" .../>
<TextBox Grid.Column="1" .../>
<Button Grid.Column="2" .../>
<Button Grid.Column="3" .../>
</Grid>
Assuming that ContentRoot is placed directly in your Window, it'll inherit it's Width and Height from the Window. Then it'll assign 200px to the 0th column, and 100px each to the 2nd and 3rd columns. And any leftover space will be assigned to the 1st column.
Of course, you can change 200, 100, and 100 to whatever you want.
If needed, add MinWidth="<value>" (replace <value> with a number) in the 1st ColumnDefnition to specify a minimum width that column must have.

WPF: TextBox resizes inside ScrollViewer [duplicate]

I have a problem.
I need to host grid with controls in ScrollViewer to prevent textbox from being either truncated or collapsed to zero-with at the UI. Also I want the with of textbox to be expanded when user change windows width.
I'm setting Window's content to following code
<DockPanel>
<TreeView DockPanel.Dock="Left" Width="150"/>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Name"
Margin="5"
VerticalAlignment="Center"/>
<TextBox Grid.Column="1"
Text="Some Name"
Margin="5"
VerticalAlignment="Center"
MinWidth="200"/>
</Grid>
</ScrollViewer>
</DockPanel>
All work fine, but when user types very long text in TextBox it is being expanded and horizontal scroll appears.
Is there any easy way to limit TextBox maximum width and allow it to be expanded only when user changes window size.
The problem is that the parent elements are providing TextBox with as much space as it thinks it needs, and when more text is present it will expand instead of staying at the initial automatic size.
One solution here is to make another auto-sized element and bind the TextBox.Width to it:
<DockPanel>
<TreeView Width="150" DockPanel.Dock="Left"/>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Margin="5" VerticalAlignment="Center" Text="Name"/>
<Border x:Name="b" Grid.Column="1" Margin="5"/>
<TextBox Width="{Binding ActualWidth, ElementName=b}"
MinWidth="200"
Grid.Column="1"
Margin="5"
VerticalAlignment="Center"
Text="Some Name"/>
</Grid>
</ScrollViewer>
</DockPanel>
Note that we set the Margin property of the auto-sizing element (the Border). This is important because if it's not set, there will be a loop:
Border width autosizes to Grid column width
TextBox width resizes to Border.ActualWidth
Grid column width resizes to TextBox width + TextBox margin
Border width autosizes to Grid column width again
By setting the Margin to the same as the TextBox, the resizing of the TextBox won't affect the Grid size.
Overriding TextBox.MeasureOverride like so worked for me:
protected override Size MeasureOverride(Size constraint)
{
Size origSize = base.MeasureOverride(constraint);
origSize.Width = MinWidth;
return origSize;
}

Text wrapping expands the column to fit the text

I've got a grid defined simply:
<Grid Margin="0,5,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition Width="48"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
Then I'm trying to bind some content like this:
<TextBlock TextWrapping="Wrap" Grid.Column="3" Text="{Binding Text}">
Set up like this, the text won't wrap. It simply expands the column to fit the text. If I set the Width to a fixed amount on the last column, wrapping works as expected. The problem there is that if the user widens the window, the column stays at a fixed size. How can I get the column to size dynamically with the width of the grid, but still wrap the text within it?
A width of "*" will split any remaining space evenly between the columns using "*". If you have a single column with Width="*", that column will get all remaining space. If you have 2 columns with Width="*", each will get 1/2 of the remaining space.
Here's a good article on grid sizing that includes star sizing.
There's one frustrating case I discovered that can break even with Width="*" and thats when you have IsSharedSizeScope= true.
<Border BorderBrush="Red" BorderThickness="1">
<StackPanel Grid.IsSharedSizeScope="True">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="G1"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="G2" />
<ColumnDefinition Width="*" SharedSizeGroup="G3" />
</Grid.ColumnDefinitions>
<TextBlock Text="Col0" Grid.Column="0" Margin="0,0,5,0"/>
<TextBlock Text="Col1" Grid.Column="1" Margin="0,0,5,0"/>
<TextBlock Text="A B C D E F G H I J K L M N O P Q R S T U V W X Y Z" TextWrapping="Wrap" Grid.Column="2"/>
</Grid>
</StackPanel>
</Border>
This won't wrap, but if you change Grid.IsSharedScopeSize to false then it does.
Haven't found a solution yet, but this can be another reason it won't work.
Try this:
<Grid Margin="0,5,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition Width="48"></ColumnDefinition>
<ColumnDefinition Name="ParentColumn" Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock TextWrapping="Wrap" Grid.Column="3" Text="{Binding Text}"
MaxWidth="{Binding ActualWidth, ElementName=ParentColumn}">
Set its width to "*"
You should use Auto only when you want to column/row to size depending on content in said column/row. If you want to "asign rest of space" use "*".
In your case, TextBlock needs to know how much space it has before acutal measure, so it can tell where to wrap the text.

Resources