I have a Grid control that is proportioned using star e.g.
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="100*" />
<ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>
However putting a long TextBlock
in the grid that overflows causes the proportions to be upset. e.g.
<TextBlock Text="Foo" Grid.Column="0" />
<TextBlock Text="Some long text here which overflows" Grid.Column="1" />
<TextBlock Text="Foo" Grid.Column="2" />
This causes the central column to be more than double the other two. How do I maintain the specified proportions? Is it possible to clip the content?
I have set TextTrimming="CharacterEllipsis"
on the TextBlocks
but no luck.
Edit
Crucially it seems, the Grid is inside a DataTemplate
, paste the following to observe the behaviour,
<!-- FallbackValue is just a quick hack to get some rows to show at design-time -->
<ListBox ItemsSource="{Binding Foo, FallbackValue=1234}"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="100*" />
<ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Foo" Grid.Column="0" />
<TextBlock Text="Some long text here which overflows" TextTrimming="CharacterEllipsis" Grid.Column="1" />
<TextBlock Text="Foo" Grid.Column="2" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The reason why this is important is that I have another Grid
as a sibling of the ListBox
which displays the 'headers' for the columns shown in the ListBox
as follows,
<Grid>
... Headers and column definitions here
</Grid>
<ListBox ...>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
... Matching column definitions here
</Grid>
</DateTemplate>
</ListBox.ItemTemplate>
</ListBox>
and so it is important that the columns match up.
I have tried to bind the ColumnDefinitions
inside the DataTemplate
to the external Grid
ColumnDefinitions
but I cannot get easily a binding reference to it.
This is one of the most annoying problems with WPF. Since the available space yielded to the templated grid is infinite, the actual content will take as much space as it wants.
The simplest way is to fix a certain width to the Grid, but that solves only the situations where there's no resizing.
Whereas you want to stretch the ListBox size (width, in the specific), unfortunately I guess that there's no any better solution other than a custom converter.
Here is my solution:
<Window.Resources>
<local:MyConv x:Key="cv1" />
</Window.Resources>
<Grid>
<ListBox
ItemsSource="{Binding Foo, FallbackValue=1234}"
HorizontalContentAlignment="Stretch"
>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}, Converter={StaticResource cv1}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="100*" />
<ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Foo" Grid.Column="0" />
<TextBlock Text="Some long text here which overflows" TextTrimming="CharacterEllipsis" Grid.Column="1" />
<TextBlock Text="Foo" Grid.Column="2" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
And the converter:
class MyConv : IValueConverter
{
public object Convert(
object value,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture
)
{
return (double)value - 30.0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Even though this is an old post I'm adding my findings as they might be relevant for other people reading this post. I had a similar issue (my * columns weren't dividing the width evenly as expected anymore, they were just sizing based on the content). The root cause here was that I had a ListView with an ItemsSource linked to a List. The ListView in WPF contains a ScrollViewer and a ScrollViewer doesn't have a fixed width. Without a fixed width a Grid can't properly determine what width to give to a * column and switches to a different sizing method.
Solution I now use an ItemsControl which doesn't contain a ScrollViewer and thus the Width is known allowing the Grid to properly size it's columns.
For more details on how exactly the Grid handles it's sizing I suggest you decompile the Grid class and have a look at the following method:
protected override Size MeasureOverride(Size constraint)
This is my MainWindow.xaml from my test application (comment out the ListView to see the difference in behaviour):
<Window x:Class="WPFSO.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfso="clr-namespace:WPFSO"
Title="MainWindow" Height="150" Width="525">
<Window.DataContext>
<wpfso:SharedSizeScopeViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type wpfso:TestViewModel}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" x:Name="SecondColumn" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" x:Name="FourthColumn" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}" />
<TextBlock Grid.Column="1" Background="LightGray" Text="{Binding Name2}"/>
<TextBlock Grid.Column="2" Text="{Binding Name3}"/>
<TextBlock Grid.Column="3" Background="Orange" Text="{Binding Name4}"/>
<!--<TextBlock Grid.Column="1" Background="Blue" HorizontalAlignment="Stretch" />
<TextBlock Grid.Column="3" Background="Orange" HorizontalAlignment="Stretch" />-->
</Grid>
</DataTemplate>
<DataTemplate x:Key="MainDataTemplate" DataType="wpfso:SharedSizeScopeViewModel" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<CheckBox Grid.Row="0" Grid.ColumnSpan="4" HorizontalAlignment="Left" FlowDirection="RightToLeft" Margin="0,0,0,25">
<TextBlock FlowDirection="LeftToRight" Text="Show differences" Style="{StaticResource LabelStyle}" />
</CheckBox>
<TextBlock Grid.Row="1" Grid.Column="0" Text="PropertyName" Style="{StaticResource LabelStyle}" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="Previous value" Style="{StaticResource LabelStyle}" />
<TextBlock Grid.Row="1" Grid.Column="3" Text="Current value" Style="{StaticResource LabelStyle}" />
<ListView Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" ItemsSource="{Binding Entries}" HorizontalAlignment="Stretch" Margin="0" HorizontalContentAlignment="Stretch"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid Name="RootGrid">
<ItemsControl ItemsSource="{Binding Entries}" />
<!--<ListView ItemsSource="{Binding Entries}" />-->
</Grid>
</Window>
The ViewModels used during this test:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WPFSO
{
public class SharedSizeScopeViewModel : INotifyPropertyChanged
{
public SharedSizeScopeViewModel()
{
var testEntries = new ObservableCollection<TestViewModel>();
testEntries.Add(new TestViewModel
{
Name = "Test",
Name2 = "Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong test",
Name3 = "Short test",
Name4 = "Nothing"
});
Entries = testEntries;
}
private ObservableCollection<TestViewModel> _entries;
public ObservableCollection<TestViewModel> Entries
{
get { return _entries; }
set
{
_entries = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
First viewmodel
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WPFSO
{
public class SharedSizeScopeViewModel : INotifyPropertyChanged
{
public SharedSizeScopeViewModel()
{
var testEntries = new ObservableCollection<TestViewModel>();
testEntries.Add(new TestViewModel
{
Name = "Test",
Name2 = "Looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong test",
Name3 = "Short test",
Name4 = "Nothing"
});
Entries = testEntries;
}
private ObservableCollection<TestViewModel> _entries;
public ObservableCollection<TestViewModel> Entries
{
get { return _entries; }
set
{
_entries = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Second viewmodel
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WPFSO
{
public class TestViewModel : INotifyPropertyChanged
{
private string _name;
private string _name2;
private string _name3;
private string _name4;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public string Name2
{
get { return _name2; }
set
{
_name2 = value;
OnPropertyChanged();
}
}
public string Name3
{
get { return _name3; }
set
{
_name3 = value;
OnPropertyChanged();
}
}
public string Name4
{
get { return _name4; }
set
{
_name4 = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With