如何使用星形来定义WPF网格列以剪裁内容

本文关键字:剪裁 网格 WPF 何使用 定义 | 更新日期: 2023-09-27 18:20:02

我有一个网格控件,它使用星形比例,例如

<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>

这一点之所以重要,是因为我有另一个Grid作为ListBox的兄弟,它显示ListBox中所示列的"标题",如下所示,

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

因此列匹配是很重要的。

我曾尝试将DataTemplate内部的ColumnDefinitions绑定到外部的Grid ColumnDefinitions,但我无法轻松获得对它的绑定引用。

如何使用星形来定义WPF网格列以剪裁内容

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

尽管这是一篇旧帖子,但我还是添加了我的发现,因为它们可能与阅读本文的其他人有关。我也遇到了类似的问题(我的*列不再像预期的那样均匀地划分宽度,它们只是根据内容调整大小)。这里的根本原因是我有一个ListView,其中ItemsSource链接到了一个List。WPF中的ListView包含一个ScrollViewer,而ScrollViewer没有固定的宽度。如果没有固定的宽度,Grid就无法正确确定给*列的宽度,并切换到不同的大小调整方法。

解决方案我现在使用了一个ItemsControl,它不包含ScrollViewer,因此宽度是已知的,允许网格正确调整列的大小。

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

设置

TextTrimming="CharacterEllipsis"

在TextBlock上。

这对我有用。正如你所定义的,中间一列的大小应该是另一列的两倍。

我发现自己也处于类似的情况,但TextTrimming不可用。最后用Converter将子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);
}