WPF UserControl - Margins Misbehaving - wpf

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.

Related

How to make text wrap in a WPF TreeViewItem?

This time, my question is as simple as it sounds... how do you get text to wrap in a WPF TreeViewItem?
I have a simple HierarchicalDataTemplate with just one TextBlock in it.
<TextBlock Text="{Binding Value}" TextWrapping="Wrap" />
The text does not wrap.
I tried binding the Width of the TextBlock to the ActualWidth of the TreeView, but although that makes the text wrap, the Width of the TreeViewItem does not fit in the TreeView because the TreeView has Padding. Binding to the ActualWidth of the TreeViewItem has (unsurprisingly) the same effect. Another downside to this is that even the items with little text stretch outside the TreeView bounds.
<TextBlock Text="{Binding Value}" TextWrapping="Wrap" Width="{Binding ActualWidth,
ElementName=TreeView}" />
Surely there must be a better way, like somehow informing the TreeViewItem of the TreeView's bounds... I can't believe it doesn't know automatically. But how can I do this?
UPDATE >>>
Thanks to H.B.'s answer, I managed to change the Grid.ColumnSpan to 2 on the Bd Border he mentioned in the ControlTemplate and it set the width so that the text now wraps nicely. The problem is that I am using this ControlTemplate for other TreeViewItems in other TreeViews where I don't want full width items.
The solution I came up with is simple. I have bound the TreeViewItem.Tag value to the Grid.ColumnSpan property in the ControlTemplate like so:
<Border Grid.ColumnSpan="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}"
Name="Bd" Grid.Column="1" ... />
This allows me to change the Grid.ColumnSpan and therefore the full width or ordinary width behaviour of the TreeViewItem by setting the TreeViewItem.Tag value to either 2 or 1 respectively.
If you look at the default template of TreeViewItems you will see a Grid like this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19"
Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ... -->
As you can see the third column takes all available space while the others are on Auto, the header is placed in the second column inside a border:
<Border Name="Bd"
Grid.Column="1"
...
This means that the column will become as large as the header, there is no restriction on it. Thus the header just gets bigger than the TreeView itself.
If you add Grid.ColumnSpan="2" to this Border it will occupy the third column as well, which is restricted by how much space is left, hence the text will wrap; this will however extend the header across the whole width which might look a bit odd when selecting it.
Of course you will also need to disable horizontal scrolling:
<TreeView ScrollViewer.HorizontalScrollBarVisibility="Disabled" ...

WPF TextBox Wrapping

I am trying to figure out how to get a textbox to wrap its contents, however the situation isn't quite the same as the typical "it doesn't wrap" scenario. My textbox is contained inside a DataTemplate which is used inside a Telerik RadTabControl instance (using a ContentTemplatePresenter to determine which view to display) and the XAML for the DataTemplate looks like this:
<DataTemplate x:Key="NotesTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Use the box below to record any general notes associated with this item." Style="{StaticResource Default}" />
<TextBox TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" GridRow="1" Margin="20" Text="{Binding Notes, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
The reason I say it doesn't fall under the normal "it doesn't wrap" scenario is it used to wrap until I had to change the view to be resizable to anything to support the varying screen sizes the app will be run on. When I did that the TextBox stopped wrapping because (presumably) as the user types something the TextBox says "I need more space" so the parent obliges and the box continues out to the right indefinitely (although the view gets scrollbars). I tried setting a MaxWidth using Binding/RelativeSource, but since the parent is specifically designed to grow that approach won't work. What I need to have happen is the box should be the width of its' containing parents' VisibleWidth. Meaning, if the Window itself is 1024x768, the TextBox's MaxWidth should be 1024 and any text thereafter would automatically wrap, but if the Window grows to 1280x1024 the box should now be 1280 and the text wrap accordingly. I tried this scenario with this binding expression, but no luck:
MaxWidth="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=ActualWidth}"
The Window size itself isn't growing so if I could get the Window's Width (minus a certain amount to cover the width of the tabs that are part of the TabControl) I believe that would work.
Any ideas?
although the view gets scrollbars
Disable the horizontal scrollView, so it will be forced to wrap. You can try to disable it on the TextBox itself, or on the wrapping Grid.
<DataTemplate x:Key="NotesTemplate">
<Grid ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Use the box below to record any general notes associated with this item." Style="{StaticResource Default}" />
<TextBox TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" Grid.Row="1" Margin="20" Text="{Binding Notes, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>

WPF ListView TextBlock TextWrapping

I am building a ListView that needs to have five columns - the first one needs to have text that can be any length and needs to wrap whenever the window size changes (in addition to changing the row height so the wrapped text is visible) and the other four columns are a static width of 45. I've been searching for hours on this and every solution I come across either requires a static width or doesn't work.
Solutions tried:
Column widths of auto, 1*, 2*, etc. (settings ignored)
DockPanel (settings ignored)
WrapPanel (ignored)
Setting Width to RelativeSource of parent for ActualWidth (ignored)
Any ideas? It seems like a significant number of people have had this same problem, but I would highly prefer to not have to go the static width route for this column. Especially since the content just gets cut off when I do that anyway (even with height="Auto" for the row). The width of the overall window could be as small as 1024, but could also be 1600+ which is why I want dynamic sizing. That way smaller screens will have the content wrap and larger screens will just show the one line since the content fits.
Here is the XAML:
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="45" />
<ColumnDefinition Width="45" />
<ColumnDefinition Width="45" />
<ColumnDefinition Width="45" />
</Grid.ColumnDefinitions>
<!-- This is the TextBlock that needs to wrap its content (and
change the height of the row (so the full content is still
visible) to whatever the available space is, but should not
make overall ListView wider than the parent's width. -->
<TextBlock Text="{Binding Content}" Padding="20,6,6,6" />
<!-- These four blocks will have other content eventually, but only need
to be 45 wide -->
<TextBlock Text="X" Grid.Column="1" HorizontalAlignment="Center" />
<TextBlock Text="X" Grid.Column="2" HorizontalAlignment="Center" />
<TextBlock Text="X" Grid.Column="3" HorizontalAlignment="Center" />
<TextBlock Text="X" Grid.Column="4" HorizontalAlignment="Center" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
Not so easy...but it can be done.
I wrote a solution for you. In short, use Expression Blend to create a copy of the ListView Template and delete the ScrollViewer surrounding the ItemPresenter.
Here is a more indepth explanation:
How to have the TextBlock in a left column of a Grid in a ListView Template expand or shrink with text wrapping?
<ListView HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
...
</ListView.ItemTemplate>
</ListView>
I'd add TextWrapping="Wrap" to the first TextBlock element.

TextBox expanding with surrounding Grid but not with text

A window has a Grid with two columns. The left column contains a control with a constant width but with a height that adapts. The right column contains a TextBox that takes up all remaining space in the Grid (and thereby in the Window).
The Grid is given a minimal width and height and is wrapped within a ScrollViewer. If the user resizes the window to be smaller than the minimal width/height of the Grid, scrollbars are displayed.
This is exactly how I want it to be. However, a problem occurs when the user starts typing text. If the text is to long to fit in one line in the TextBox, I want the text to wrap. Therefore I set TextWrapping="Wrap" on the TextBox. But since the TextBox has an automatic width and is wrapped in a ScrollViewer (its actually the whole Grid that is wrapped), the TextBox just keeps expanding to the right.
I do want the TextBox to expand if the window is expanded, but I don't want the TextBox to expand by the text. Rather the text should wrap inside the available TextBox. If the text don't fit within the TextBox height, a scrollbar should be displayed within the TextBox.
Is there a way to accomplish this?
Below is some code that shows my problem:
<Window x:Class="AdaptingTextBoxes.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="400" Background="DarkCyan">
<Grid Margin="10" Name="LayoutRoot">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid MinWidth="300" MinHeight="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Margin="0,0,10,0" Content="Button" Width="100" />
<TextBox Grid.Column="1" AcceptsReturn="True" TextWrapping="Wrap" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" />
</Grid>
</ScrollViewer>
</Grid>
</Window>
You could use an invisible border (its hacky but it works - its how I tend to sort out dynamic textbox sizes in Xaml):
<Border BorderThickness="0" x:Name="border" Grid.Column="1" Margin="0.5" />
<TextBox Grid.Column="1" AcceptsReturn="True" TextWrapping="Wrap" Width="{Binding ActualWidth, ElementName=border}" Height="{Binding ActualHeight, ElementName=border}" />
Have you tried setting the MaxWidth property on just the TextBox?
Edit after OP's comment
I would try getting rid of the ScrollViewer. The sizing used in the Grid's layout should take care of re-sizing and the scroll bar settings on the TextBox should take care of the rest.
The answer is based on Leom's answer.
The solution works great when you enlarge the window, but the resizing is not smooth when you make the window smaller. As the textbox participates in the grid's layout, it has to perform layout process multiple times. You can fix that by putting the texbox in the canvas, so the change of the size of the textbox no longer triggers the grid's re-layout.
The updated code:
<Border BorderThickness="0" x:Name="border" Grid.Column="1" Margin="0.5" />
<Canvas Grid.Column="1">
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Width="{Binding ActualWidth, ElementName=border}" Height="{Binding ActualHeight, ElementName=border}" />
</Canvas>

WPF Grid Column MaxWidth not enforced

This problem stems from not being able to get my TextBlock to wrap. Basically as a last-ditch attempt I am setting MaxWidth on my container grid's columns. I was surprised to find that my child label and textbox still do whatever they want (bad children, BAD) and are not limited by my grid column's MaxWidth="200".
What I'm really trying to do is let my TextBlock fill available width and wrap if necessary. So far after trying many variations of HorizontalAlignment="Stretch" on every known parent in the universe, nothing works, except setting an explicit MaxWidth="400" or whatever number on the TextBlock. This is not good because I need the TextBlock to fill available width, not be limited by some fixed number. Thanks!
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MaxWidth="200" SharedSizeGroup="A" />
<ColumnDefinition MaxWidth="200" SharedSizeGroup="B" />
</Grid.ColumnDefinitions>
<Label VerticalAlignment="Top" Margin="0 5 0 0" Grid.Column="0" Style="{StaticResource LabelStyle}" Width="Auto" Content="{Binding Value.Summary}" />
<TextBlock Grid.Column="1" Margin="5,8,5,8" FontWeight="Normal"
Background="AliceBlue"
Foreground="Black" Text="{Binding Value.Description}"
HorizontalAlignment="Stretch"
TextWrapping="Wrap" Height="Auto" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I've tried to replicate your problem by pasting everything between your Grid elements in to Kaxaml but everything wraps as you would expect it to. (I inserted regular strings where you were doing bindings and removed the Label style).
It could be that the problem is higher up the tree.
I'd suggest pasting chunks in to Kaxaml or similar to test and see which parent breaks your UI.
I provided an answer to this question, only it was using an ListView instead of an ItemsControl but the issue is likely the same. There is probably a ScrollViewer surrounding your ItemPresenter and you need to edit a copy of the ItemsControl template.
WPF ListView TextBlock TextWrapping

Resources