如何使WPF网格的星号列定义裁剪内容?

6

我有一个使用星号比例分配大小的网格控件,例如:

<Grid.ColumnDefinitions>
    <ColumnDefinition Width="50*" />
    <ColumnDefinition Width="100*" />
    <ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>

然而,将一个过长的TextBlock放在网格中会导致比例失调。例如:

<TextBlock Text="Foo" Grid.Column="0" />
<TextBlock Text="Some long text here which overflows" Grid.Column="1" />
<TextBlock Text="Foo" Grid.Column="2" />

这会导致中心列的宽度是其他两列的两倍以上。如何保持指定的比例?可以裁剪内容吗?
我已经在TextBlocks上设置了TextTrimming="CharacterEllipsis",但没有成功。
重要的是,网格位于DataTemplate内,请将以下内容粘贴以观察行为:
<!-- 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>

这个很重要的原因是我有另一个作为ListBox同级的Grid,它显示在ListBox中展示的列的“标题”,如下所示:
<Grid>
    ... Headers and column definitions here
</Grid>

<ListBox ...>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid>
                ... Matching column definitions here
            </Grid>
        </DateTemplate>
    </ListBox.ItemTemplate>
</ListBox>

因此,确保列匹配非常重要。

我尝试将 DataTemplate 中的 ColumnDefinitions 绑定到外部 GridColumnDefinitions,但是我不能方便地获取到绑定引用。


2
我无法通过创建新的WPF应用程序并将此xaml粘贴到窗口中来重现。您确定已经在这里放置了所有模板/代码吗?可能存在干扰的样式/触发器/模板/动画。尝试提供问题的最小表示。 - Emond
通常情况下,“auto”宽度会出现这种问题,“*”将限制宽度,除非其父级容器中有任何“auto”宽度。 - pushpraj
1
我已经复制了它,并且使用'TextTrimming="CharacterEllipsis"'正确地进行了修剪。 - AxdorphCoder
@AxdorphCoder,@Erno Ok,这里的关键部分似乎是Grid位于ListBoxDataTemplate内。 - Brendan
4个回答

7
这是WPF中最令人烦恼的问题之一。由于模板化网格可用空间是无限的,实际内容将占据它想要的所有空间。
最简单的方法是给网格固定宽度,但这只解决了没有调整大小的情况。
而当你想要拉伸ListBox的大小(特别是宽度)时,不幸的是我认为除了自定义转换器之外没有更好的解决方案。
这是我的解决方案:
<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>

以及转换器:

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();
    }
}

2
即使这是一个旧帖子,但我还是想分享我的发现,因为它们可能对其他人有用。我遇到了类似的问题(我的*列不再按预期均匀分配宽度,它们只是根据内容调整大小)。根本原因在于我有一个ListView,其ItemsSource链接到一个List。WPF中的ListView包含一个ScrollViewer,而ScrollViewer没有固定的宽度。没有固定宽度,Grid无法确定要给*列多少宽度并切换到不同的调整方法。

解决方案 现在我使用一个不包含ScrollViewer的ItemsControl,因此宽度是已知的,可以使Grid正确地调整其列宽。

有关Grid如何处理其大小调整的更多详细信息,建议您反编译Grid类并查看以下方法:

protected override Size MeasureOverride(Size constraint) 

这是我的测试应用程序的MainWindow.xaml文件(将ListView注释掉以查看行为差异):

 <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));
        }
    }
}

第一个视图模型

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));
        }
    }
}

第二个视图模型

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));
        }
    }
}

0

设置

TextTrimming="CharacterEllipsis"

在TextBlock上。

这对我有用。因为您已经定义了中间列应该是其他列的两倍大小。


0

我发现自己处于类似的情况,但是没有可用的TextTrimming

最终将子元素的Width绑定到Grid.ActualWidth,并使用转换器将比例转换为绝对宽度。

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="50*" />
        <ColumnDefinition Width="100*" />
        <ColumnDefinition Width="50*" />
    </Grid.ColumnDefinitions>

    <Grid.Resources>
        <local:PartConv x:Key="partConv"/>
        <sys:Double x:Key="r0">0.25</sys:Double>
        <sys:Double x:Key="r1">0.5</sys:Double>
        <sys:Double x:Key="r2">0.25</sys:Double>
    </Grid.Resources>

    <TextBlock Text="Foo" Grid.Column="0" 
        Width="{Binding ActualWidth, 
        RelativeSource={RelativeSource AncestorType=Grid}}, 
        Converter={StaticResource partConv}, 
        ConverterParameter={StaticResource r0}}"/>

    <TextBlock Text="Some long text here which overflows" Grid.Column="1" 
        Width="{Binding ActualWidth, 
        RelativeSource={RelativeSource AncestorType=Grid}}, 
        Converter={StaticResource partConv}, 
        ConverterParameter={StaticResource r1}}"/>

    <TextBlock Text="Foo" Grid.Column="2" 
        Width="{Binding ActualWidth, 
        RelativeSource={RelativeSource AncestorType=Grid}}, 
        Converter={StaticResource partConv}, 
        ConverterParameter={StaticResource r2}}"/>
</Grid>

[ValueConversion(typeof(double), typeof(double))]
public class PartConv : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
        => ((double)value) * ((double)parameter);
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
        => ((double)value) / ((double)parameter);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接