Prevent a TextBox from horizontal expanding in WPF - 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;
}
}

Related

WPF XAML ListView - Make TextBlock Text wrap

I have a ListView with ListView.ItemTemplate like this
<ListView
x:Name="myList"
BorderBrush="Transparent"
ItemsSource="{Binding MyItems}"
SelectedIndex="0"
ScrollViewer.CanContentScroll="True"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Margin="5,5,5,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/> <--THIS WILL FORCE WRAPPING
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding FilePath}"
Grid.Row="0" Margin="3,3,3,3"
Style="{StaticResource MyFilePathTextLabel}"
TextWrapping="WrapWithOverflow"/> <-- THIS WILL NOT WRAP TEXT
<StackPanel Orientation="Horizontal" Grid.Row="1" Margin="3,3,3,3">
<TextBlock Text="Lab location: "/>
<TextBlock Text="{Binding LabLocation}"
Style="{StaticResource MyLabLocationTextLabel}"/>
</StackPanel>
...
...
</DataTemplate>
</ListView.ItemTemplate>
...
...
</ListView>
This will show ListView items like this:
----------------------------------
C:/samples/folderA/myfile1.txt <-- NO WRAP AS IT FITS
Lab location: Chemistry Lab 301
----------------------------------
C:/samples/folderA/folderB/fold
erC/folderD/folderE/folderF/myf
ile2.txt <-- WRAP SINCE NOT FITTING
Lab location: Chemistry Lab 301
----------------------------------
C:/samples/folderA/folderB/myfi
le3.txt <-- WRAP SINCE NOT FITTING
Lab location: Chemistry Lab 301
----------------------------------
C:/samples/folderA/folderB/fold
erC/folderD/folderE/folderF/fol
derG/folderH/folderI/folderJ/fo
lderK/myfile4.txt <-- WRAP SINCE NOT FITTING
Lab location: Chemistry Lab 301
----------------------------------
C:/samples/myfile5.txt <-- NO WRAP AS IT FITS
Lab location: Chemistry Lab 301
----------------------------------
Above, each item show file location as wrapped if it does not fit the width of the ListView.
UPDATE:
Updated XAML
UPDATE 2:
Setting the column Width of grid container to hardcoded value of will force wrapping (see above commented line). But since form is resizable, the grid and ListView is also resizable. Therefore, I can not hardcode width. It needs to wrap according to the current size of the form.
Set the HorizontalContentAlignement="Stretch" on the ListView object itself to tell it to stretch it's Content horizontally to fit available space, and set the HorizontalScrollBarVisiblilty to Disabled to make sure horizontal scrolling is disabled.
<ListView x:Name="myList" ...
HorizontalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
If you use a Grid and set <ColumnDefinition Width="*">, the GridColumn enlarges as much as possible to fill all the available space. Only and only after that, other operations like wrapping take place.
In this case, the GridColumn becomes large enough to contain all the text on a single line. And that's the reason why the text doesn't wrap: it doesn't need to wrap! It has all the space it needs to stay on a single line!
SOLUTION: Set a fixed column width, as 200, or 100, or anyway try a smaller width, and see the result. At some point the text MUST wrap, with a GridColumn thin enough.
SOLUTION FOR FLEXIBLE WIDTH:
You have to bind the Width of the inner Grid (the one with the RowDefinitions) to the ActualWidth of the outer Grid (the one with the ColumnDefinitions).
Create a converter like this:
public class OuterGridToInnerGridWidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((double)value) / 2;
}
}
In this example I suppose that the inner Grid has half the Width of the outer. If instead you have the column of the Grid with Width="*" and the second column with a fixed width of - for example - 50, the converter can be:
public class OuterGridToInnerGridWidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((double)value) - 50;
}
}
Made this, add this attribute to the inner Grid:
Width="{Binding ActualWidth,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid}},
Converter={StaticResource OuterGridToInnerGridWidthConverter}}"
Finally, set HorizontalContentAlignment="Stretch" for the ListView.
This works both when you make the window smaller or bigger: the TextBlock resizes and wraps correctly.
Can you try to have the first RowDefinition like :
<RowDefinition Height="Auto"/>
instead of
<RowDefinition Height="*"/>
?
If no success, try temporarily removing the
Style="{StaticResource MyFilePathTextLabel}"
also. You did not share its code, so I'm thinking it might break the wrapping.

How to make scrollviewer work with Height set to Auto in WPF?

I have learned that if the height of a grid row, where the ScrollViewer resides, is set as Auto, the vertical scroll bar will not take effect since the actual size of the ScrollViewer can be larger than the height in sight. So in order to make the scroll bar work, I should set the height to either a fixed number or star height
However, I now have this requirement, that I have two different views reside in two grid rows, and I have a toggle button to switch between these two views: when one view is shown, the other one is hidden/disappeared. So I have defined two rows, both heights are set as Auto. And I bind the visibility of the view in each row to a boolean property from my ViewModel (one is converted from True to Visible and the other from True to Collapsed. The idea is when one view's visibility is Collapsed, the height of the grid row/view will be changed to 0 automatically.
The view show/hidden is working fine. However, in one view I have a ScrollViewer, which as I mentioned doesn't work when the row height is set as Auto. Can anybody tell me how I can fulfill such requirement while still having the ScrollViewer working automatically`? I guess I can set the height in code-behind. But since I am using MVVM, it would require extra communication/notification. Is there a more straightforward way to do that?
In MVVM, the way that worked for me was to bind the height of the ScrollViewer to the ActualHeight of the parent control (which is always of type UIElement).
ActualHeight is a read-only property which is only set after the control has been drawn onto the screen. It may change if the window is resized.
<StackPanel>
<ScrollViewer Height="{Binding Path=ActualHeight,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UIElement}}">
<TextBlock Text=Hello"/>
</ScrollViewer>
</StackPanel>
But what if the parent control has an infinite height?
If the parent control has an infinite height, then we have a bigger problem. We have to keep setting the height of all parents, until we hit a control with a non-infinite height.
Snoop is absolutely invaluable for this:
If the "Height" for any XAML element is 0 or NaN, you can set it to something using one of:
Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UIElement}}"
VerticalAlignment="Stretch"
Height="Auto"
Hint: Use VerticalAlignment="Stretch" if you are a child of a Grid with a <RowDefinition Height="*">, and the Binding RelativeSource... elsewhere if that doesn't work.
If you're interested, here is all of my previous attempts to fix this issue:
Appendix A: Previous Attempt 1
Can also use this:
Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=StackPanel}}"
Appendix B: Previous Attempt 2
Useful info: see Auto Height in combination with MaxHeight.
If nothing seems to work, it's probably because the ActualHeight of the parent is either 0 (so nothing is visible) or huge (so the scrollviewer never needs to appear). This is more of a problem if there are deeply nested grids, with a scrollviewer right at the bottom.
Use Snoop to find the ActualHeight of the parent StackPanel. In properties, filter by the word "Actual", which brings back ActualHeight and ActualWidth.
If ActualHeight is zero, give it a minimum height using MinHeight, so we can at least see something.
If ActualHeight is so huge that it goes off the edge of the screen (i.e. 16,000), give it a reasonable maximum height using MaxHeight, so the scrollbars will appear.
Once the scrollbars are appearing, then we can clean it up further:
Bind the Height of the StackPanel or Grid to the ActualHeight of the parent.
Finally, put a ScrollViewer inside this StackPanel.
Appendix C: Previous Attempt 3
It turns out that this can sometimes fail:
Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=StackPanel}}"
The reason? It the binding fails, the height will be zero and nothing will be seen. The binding can fail if we are binding to an element which is not accessible. The binding will fail if we are going up the visual tree, then down to a leaf node (e.g. up to the parent grid, then down to the ActualHeight of a row attached to that grid). This is why binding to the ActualWidth of a RowDefinition simply won't work.
Appendix D: Previous Attempt 4
I ended up getting this working by making sure that Height=Auto for all of the parent elements from us to the first <Grid> element in the UserControl.
Change Height from Auto to *, if you can.
Example:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="200" Width="525">
<StackPanel Orientation="Horizontal" Background="LightGray">
<Grid Width="100">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="_scroll1">
<Border Height="300" Background="Red" />
</ScrollViewer>
<TextBlock Text="{Binding ElementName=_scroll1, Path=ActualHeight}" Grid.Row="1"/>
</Grid>
<Grid Width="100">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="_scroll2">
<Border Height="300" Background="Green" />
</ScrollViewer>
<TextBlock Text="{Binding ElementName=_scroll2, Path=ActualHeight}" Grid.Row="1"/>
</Grid>
</StackPanel>
</Window>
I've had similar problem, taking me hours to figure out the solution. What solved it was using a Dockpanel as parent container instead of a StackPanel. Just specify all children to dock to top if the functionality should be similar to vertical stackpanel. Consider using LastChildFill="False" in the Dock XAML which is'n default.
So instead of:
<StackPanel Orientation="Horizontal">
<Textbox>SomeTextBox</Textbox>
<Scrollviewer/>
</StackPanel>
Try:
<DockPanel LastChildFill="False">
<Textbox DockPanel.Dock="Top">SomeTextBox</Textbox>
<Scrollviewer DockPanel.Dock="Top"/>
</DockPanel>
You can either set a fix height on your ScrollViewer but then you have to consider that the second row of your grid will have that height too since row's first child will be the ScrollViewer and row's height is auto, or you bind the height of ScrollViewer to another control in your layout. We don't know how your layout looks alike.
At the end if you don't like neither of both just set the row's height to * as swiszcz suggested or hack wpf write your own custom panel that will be able to layout everything possible in every parallel universe or something like that. :)
What I discover is that you have to put your ScrollViewer within a container that has Height=Auto or you get his parent Heigh Actual Size and apply it to that container.
In my case I have UserControl like
<Grid Margin="0,0,0,0" Padding="0,2,0,0">
<ScrollViewer Height="Auto" ZoomMode="Disabled" IsVerticalScrollChainingEnabled="True" VerticalAlignment="Top"
HorizontalScrollMode="Enabled" HorizontalScrollBarVisibility="Disabled"
VerticalScrollMode="Enabled" VerticalScrollBarVisibility="Visible">
<ListView ItemsSource="{x:Bind PersonalDB.View, Mode=OneWay}" x:Name="DeviceList"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
ItemTemplate="{StaticResource ContactListViewTemplate}"
SelectionMode="Single"
ShowsScrollingPlaceholders="False"
Grid.Row="1"
Grid.ColumnSpan="2"
VerticalAlignment="Stretch"
BorderThickness="0,0,0,0"
BorderBrush="DimGray">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel AreStickyGroupHeadersEnabled="False" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate x:DataType="local1:GroupInfoList">
<TextBlock Text="{x:Bind Key}"
Style="{ThemeResource TitleTextBlockStyle}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</ScrollViewer>
</Grid>
And I add it dinamically to ContentControl which is within a Page.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="0,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="70" />
<RowDefinition Height="*" MinHeight="200" />
</Grid.RowDefinitions>
<Grid Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<ContentControl x:Name="UIControlContainer" />
</Grid>
</Grid>
Notice that Heigh of the Row is *
When I populate ContentControl I use this code in Loaded event
UIControlContainer.Content = new UIDeviceSelection() {
VerticalAlignment = VerticalAlignment.Stretch,
HorizontalAlignment = HorizontalAlignment.Stretch,
Height = UIControlContainer.ActualHeight,
Width = UIControlContainer.ActualWidth
};
And also when ContentControl changes its size you have to update size of the UserControl.
UIControlContainer.SizeChanged += UIControlContainer_SizeChanged;
private void UIControlContainer_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (UIControlContainer.Content != null)
{
if (UIControlContainer.Content is UserControl)
{
(UIControlContainer.Content as UserControl).Height = UIControlContainer.ActualHeight;
(UIControlContainer.Content as UserControl).Width = UIControlContainer.ActualWidth;
}
}
}
Enjoy!
P.S. Acctually I did it for UWP.

WPF UserControl - Margins Misbehaving

I'm fairly new to WPF custom controls and have started by playing with a basic "LabelEdit" - basically a Label control and a TextBox. I have binding for 4 properties - Text, Label, TextWidth and LabelWidth (perhaps not what you would name them in a production environment, but this is just so that I can educate myself!).
All seems to work well. I also have an event which fires when the size of the label changes and causes an "ActualLabelWidth" DependencyProperty to change, so that a series of LabelEdit controls can all have the same Label Width. Here's the XAML for the LabelEdit:
<Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
<Grid.RowDefinitions >
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding LabelWidth}" />
<ColumnDefinition Width="{Binding TextWidth}" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Grid.Row="0" Content="{Binding Label, FallbackValue=LabelEdit}" SizeChanged="Label_SizeChanged" />
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding Text}" />
</Grid>
...and for the MainWindow using it:
<ajdata:LabelEdit Text="{Binding Title}" Label="Title:" LabelWidth="{Binding ElementName=lblForename, Path=ActualLabelWidth}" TextWidth="100" />
<ajdata:LabelEdit Text="{Binding Surname}" Label="Surname:" LabelWidth="{Binding ElementName=lblForename, Path=ActualLabelWidth}" TextWidth="300" />
<ajdata:LabelEdit Text="{Binding Forename}" Label="Forename(s):" LabelWidth="Auto" TextWidth="300" Name="lblForename" />
So the label with the largest piece of text sets the width for the others.
The problem occurs when I give the Label a margin ("0,0,5,0") to space it from the TextBox element. In this situation, the LabelEdit with the "Auto" width seems to work fine. The bound versions, however, appear not to honour the margins. This means the TextBox part of the element appears positioned left of where it should be.
Does anyone know what I need to do so that all the Labels end up the same width with margins taken in to account? I realise I could probably insert an extra piece of code in my event handler, but would rather make the XAML do its job, if possible. Many thanks.
I have now solved this (but am still perhaps unsure why). Instead of setting margins of "0,0,5,0" on the label, I have set margins of "5,0,0,0" on the TextBox. This way, everything remains nicely lined up, whatever the label width.

Textbox will not stretch to fill viewbox

I want the font size of my labels and textboxes in my LOB form to grow and shrink with window resize or resolution change. To achieve this I've placed my labels and textboxes within viewboxes.
The labels and custom radio buttons behave as I expect, but the textboxes will not stretch horizontally to fill the viewbox (sorry can't post image because of rep). The textboxes will horizontally fill the viewbox if you type into them.
Here is an example of the code which I am working with:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.186*"/>
<ColumnDefinition Width="0.814*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.127*"/>
<RowDefinition Height="0.873*"/>
</Grid.RowDefinitions>
<Viewbox Margin="0,0,0.917,0">
<Label Content="First name:"/>
</Viewbox>
<Viewbox Grid.Column="1">
<TextBox TextWrapping="Wrap"/>
</Viewbox>
</Grid>
I've tried placing grid, stackpanel and dockpanel (with LastChildFill="True") within the viewbox and then placing the textboxes within these layout controls but this didn't work either. Is there anyway to get the textboxes to horizontally fill the parent viewbox?
This problem is similar to this: WPF TextBox won't fill in StackPanel but this problem is with stackpanels, not viewboxes.
I think what you want is not easily possible. ViewBox tells its children that they have inifite space, in the measure pass of the layouting. After that, the display is fit into the viewbox. Now a textbox, told to have infinite space, obviously can't stretch.
I have 2 solutions, which i think are not what you want, but might be helpful anyway.
The first:
<Viewbox Grid.Column="1" Grid.Row="0" Stretch="Uniform" >
<Grid Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Viewbox}}, Path=ActualWidth}">
<TextBox TextWrapping="Wrap" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Grid>
</Viewbox>
this will stretch you textbox infact, but disable the behaviour expected from the viewbox. Why? We told the ViewBox to keep the aspect ratio, and set the textbox to fill the whole width of the viewbox, which keeps the size of the textbox.
The second:
would be to add a height to the grid, taken from the label, modified with the scaletransform of its viewbox.
This one i haven't tried, but it would involve a value converter.
In conclusion: There is no easy way to achieve what you want, because of the way the viewbox layouts its children. If you just want to stretch horizontally, my first solution works fine, if you want to scale. My guess is you have to do it yourself.
If what you want doesn't work/isn't easy then fake it:
<TextBox GotFocus="TextBox_GotFocus" HorizontalAlignment="Stretch" BorderThickness="0" Grid.Column="1"/>
<Viewbox HorizontalAlignment="Left" Stretch="Uniform" Grid.Column="1">
<TextBox x:Name="NameTextBox" Width="50" TextWrapping="Wrap" Text="Text" BorderThickness="0"/>
</Viewbox>
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
NameTextBox.Focus();
NameTextBox.SelectionStart = NameTextBox.Text.Length;
}
Basically what happens is another TextBox is behind the Viewbox and when the behind TextBox gets focus, it switches focus to the Viewbox's TextBox. This will produce some odd resizing since you have your grid setup with relative sizes. You will need to play around with your grid column/width sizes until you get the effect you desire.
Just use a converter
Set the FontSize like this:
FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight ,Converter={StaticResource heightconverter}, ConverterParameter=3}"
public class ProcentualHeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is string p)
{
if (value is double v)
{
var result = double.TryParse(p, out double param);
if (result)
{
return v / param;
}
}
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Wrapping panels in WPF

I have the following layout in my window:
Grid with two columns
GridSplitter which resizes grid columns
Second grid column is filled with StackPanel
StackPanel is oriented vertically and has 2 children: TextBlock and a WrapPanel
WrapPanel has two Grids as children
First Grid child contains one Image
Second Grid contains a StackPanel with 3 TextBlocks oriented vertically.
The XAML code looks like this:
<Window>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Left" />
<StackPanel Grid.Column="1" Margin="5,0,0,0" Orientation="Vertical"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Text="Now here's a silly poem for you." />
<WrapPanel>
<Grid Name="GridForImage">
<Image Width="200" Height="200" Source="Image.jpg" />
</Grid>
<Grid Name="GridForText">
<StackPanel Orientation="Vertical">
<TextBlock TextWrapping="WrapWithOverflow" Text="Roses are red." />
<TextBlock TextWrapping="WrapWithOverflow" Text="Violets are blue." />
<TextBlock TextWrapping="WrapWithOverflow" Text="You belong in a zoo." />
</StackPanel>
</Grid>
</WrapPanel>
</StackPanel>
</Grid>
</Window>
Once the window opens, the second column is wide enough to allow grids GridForImage and GirdForText to be placed next to each other horizontally. If I shrink the width of the second column using the grid splitter, the GridForText grid gets placed underneath the GridForImage at one point, which is quite expected.
Here's what I would like to achieve:
I want GridForText to shrink its width to a certain size and to remain positioned to the right of the GridForImage, as I move the grid splitter to the right side of the window. Then, when the width shrinks to a certain value, say 200px, it should get placed underneath the GridForImage, i.e. WrapPanel should do its magic. Right now, the GridForText doesn't resize at all, it just gets placed underneath when it's current width becomes too large for the width of the WrapPanel.
When the GridForText does get placed underneath the GridForImage, I want GridForImage to fill the entire width of the WrapPanel's width.
Is all this possible and what should I do? Thank you all.
You're essentially trying to use two distinct layout modes so you just need to set up the two distinct states in your layout and then add bindings or triggers to switch between them at the point when you want to switch modes (i.e. width = 200). Using a Grid is the most flexible and gives you a lot more control over the relative sizes but requires more settings and would work best in a ControlTemplate or DataTemplate where you can use Triggers to set a bunch of things at once based on a condition.
Here's a more compact example using UniformGrid with some Bindings and a converter. I removed the fixed sizing on the Image - try Stretch="Fill" if you care more about filling width than aspect ratio. I also changed one StackPanel to a DockPanel to maintain vertical stretching for its children and added a Background to one of the TextBlocks just to show how much Width it's really getting:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Left" />
<DockPanel Grid.Column="1" Margin="5,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Text="Now here's a silly poem for you." DockPanel.Dock="Top"/>
<UniformGrid Rows="{Binding RelativeSource={RelativeSource Self}, Path=ActualWidth, Converter={x:Static local:LayoutModeConverter.Row}, ConverterParameter=200}"
Columns="{Binding RelativeSource={RelativeSource Self}, Path=ActualWidth, Converter={x:Static local:LayoutModeConverter.Column}, ConverterParameter=200}">
<Image Source="Image.jpg" />
<StackPanel Orientation="Vertical">
<TextBlock TextWrapping="WrapWithOverflow" Text="Roses are red." Background="Red" />
<TextBlock TextWrapping="WrapWithOverflow" Text="Violets are blue." />
<TextBlock TextWrapping="WrapWithOverflow" Text="You belong in a zoo." />
</StackPanel>
</UniformGrid>
</DockPanel>
</Grid>
And the converter:
public class LayoutModeConverter : IValueConverter
{
public static readonly LayoutModeConverter Row = new LayoutModeConverter { RowMode = true };
public static readonly LayoutModeConverter Column = new LayoutModeConverter { RowMode = false };
public bool RowMode { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double width = System.Convert.ToDouble(value);
double targetWidth = System.Convert.ToDouble(parameter);
if (RowMode)
return width > targetWidth ? 1 : 2;
else
return width > targetWidth ? 2 : 1;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Resources