How to have the TextBlock in a left column of a Grid in a ListView Template expand or shrink with text wrapping?
Ok, lets say you want to have a Grid where each item is a row of data in a Grid. The left most column should expand or shrink, and yes the text should wrap when it shrinks.
Not so easy…but it can be done if you use the right tools.
- Use a DockPanel not a Grid
- Make the left most column the last one added
<Window x:Class="ListBoxWithWrap.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="242" d:DesignWidth="388" > <Grid> <DockPanel Name="MainGrid" HorizontalAlignment="Stretch"> <!-- These four blocks will have other content eventually, but only need to be 45 wide --> <TextBlock Text="X" Grid.Column="1" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/> <TextBlock Text="X" Grid.Column="2" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/> <TextBlock Text="X" Grid.Column="3" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/> <TextBlock Text="X" Grid.Column="4" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/> <!-- 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="A very long string that should wrap when the window is small." Padding="20,6,6,6" TextWrapping="Wrap" DockPanel.Dock="Right"/> </DockPanel> </Grid> </Window>
You will see that this works as you desire.
Now put this in a ListView’s Template and set it to use Binding.
<Window x:Class="ListBoxWithWrap.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="242" d:DesignWidth="388" SizeToContent="WidthAndHeight"> <Grid> <ListView Name="lvWrap" ItemsSource="{Binding}"> <ListView.ItemTemplate> <DataTemplate> <DockPanel Name="MainGrid" HorizontalAlignment="Stretch"> <!-- These four blocks will have other content eventually, but only need to be 45 wide --> <TextBlock Text="X" Grid.Column="1" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/> <TextBlock Text="X" Grid.Column="2" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/> <TextBlock Text="X" Grid.Column="3" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/> <TextBlock Text="X" Grid.Column="4" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/> <!-- 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" TextWrapping="Wrap" DockPanel.Dock="Right"/> </DockPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> </Window>
Now give it some data to bind to.
using System.Collections.Generic; using System.Windows; using System.Windows.Documents; namespace ListBoxWithWrap { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); List<SomeItem> list = new List<SomeItem>(); list.Add(new SomeItem() { Content = "Some very long string with so many words there should be some wrapping going on to prevent a line of text that is too long" }); list.Add(new SomeItem() { Content = "Some very long string with so many words there should be some wrapping going on to prevent a line of text that is too long" }); list.Add(new SomeItem() { Content = "Some very long string with so many words there should be some wrapping going on to prevent a line of text that is too long" }); list.Add(new SomeItem() { Content = "Some very long string with so many words there should be some wrapping going on to prevent a line of text that is too long" }); list.Add(new SomeItem() { Content = "Some very long string with so many words there should be some wrapping going on to prevent a line of text that is too long" }); lvWrap.DataContext = list; } public class SomeItem { public string Content { get; set; } } } }
The shrink with text wrapping no longer works once inside of the ListView. So that tells you that something to do with the ListView is breaking the feature you want.
Here is how you fix this:
1. Open your project in Expression Blend. (If you don’t have Expression Blend, maybe just look at my code below and copy it)
2. Right-Click on the ListView in the Object and Timeline tab and choose Edit Template | Edit a Copy.
3. Click OK on the next Window.
This will create the following resource code.
<Window.Resources> <SolidColorBrush x:Key="ListBorder" Color="#828790"/> <Style x:Key="ListViewStyle1" TargetType="{x:Type ListView}"> <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/> <Setter Property="BorderBrush" Value="{StaticResource ListBorder}"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.CanContentScroll" Value="true"/> <Setter Property="ScrollViewer.PanningMode" Value="Both"/> <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListView}"> <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="1" SnapsToDevicePixels="true"> <ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}"> <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> </ScrollViewer> </Border> <ControlTemplate.Triggers> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/> </Trigger> <Trigger Property="IsGrouping" Value="true"> <Setter Property="ScrollViewer.CanContentScroll" Value="false"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources>
4. Now look at what is surrounding the ItemPresenter. Yes, you see the ScrollViewer, which is your problem. Delete it.
5. Build you project.
Success! Now your feature to both expand or shrink with text wrapping is back.
Here is the final XAML.
<Window x:Class="ListBoxWithWrap.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="242" d:DesignWidth="388" SizeToContent="WidthAndHeight"> <Window.Resources> <SolidColorBrush x:Key="ListBorder" Color="#828790"/> <Style x:Key="ListViewStyle1" TargetType="{x:Type ListView}"> <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/> <Setter Property="BorderBrush" Value="{StaticResource ListBorder}"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.CanContentScroll" Value="true"/> <Setter Property="ScrollViewer.PanningMode" Value="Both"/> <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListView}"> <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="1" SnapsToDevicePixels="true"> <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/> </Trigger> <Trigger Property="IsGrouping" Value="true"> <Setter Property="ScrollViewer.CanContentScroll" Value="false"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources> <Grid> <ListView Name="lvWrap" ItemsSource="{Binding}" Style="{DynamicResource ListViewStyle1}"> <ListView.ItemTemplate> <DataTemplate> <DockPanel Name="MainGrid" HorizontalAlignment="Stretch"> <!-- These four blocks will have other content eventually, but only need to be 45 wide --> <TextBlock Text="X" Grid.Column="1" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/> <TextBlock Text="X" Grid.Column="2" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/> <TextBlock Text="X" Grid.Column="3" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/> <TextBlock Text="X" Grid.Column="4" HorizontalAlignment="Center" Width="45" DockPanel.Dock="Right"/> <!-- 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" TextWrapping="Wrap" DockPanel.Dock="Right"/> </DockPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </Grid> </Window>
You should now have a little bit more understanding of the ListView template and how to manipulate it, which should translate to other objects in WPF as well.