如何获取使用星形定义的WPF网格列来剪辑内容?

时间:2014-08-29 11:34:42

标签: c# wpf xaml

我有一个使用星形比例的网格控件。

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

但是,在网格中放置一个长TextBlock会导致比例被打乱。 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" />

这导致中心列比另外两列多两倍。如何保持指定的比例?是否可以剪辑内容?

我在TextTrimming="CharacterEllipsis"上设置了TextBlocks,但没有运气。

修改

至关重要的是,网格位于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中显示的列的“标题”,如下所示: / p>

<Grid>
    ... Headers and column definitions here
</Grid>

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

所以列匹配很重要。

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

4 个答案:

答案 0 :(得分:6)

这是WPF最烦人的问题之一。由于产生于模板化网格的可用空间是无限的,因此实际内容将占用所需的空间。

最简单的方法是为Grid固定一定的宽度,但这只能解决没有调整大小的情况。

虽然您想要拉伸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();
    }
}

答案 1 :(得分:1)

虽然这是一篇旧帖子,但我正在添加我的发现,因为它们可能与阅读这篇文章的其他人有关。 我有一个类似的问题(我的*列没有按预期平均分配宽度,它们只是根据内容调整大小)。 这里的根本原因是我有一个ListView,其中ItemsSource链接到List。 WPF中的ListView包含ScrollViewer,而ScrollViewer没有固定宽度。 如果没有固定宽度,网格无法正确确定要为*列提供的宽度,并切换到不同的大小调整方法。

<强>解决方案 我现在使用一个不包含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));
        }
    }
}

答案 2 :(得分:0)

设置

TextTrimming="CharacterEllipsis"

在TextBlock上。

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

答案 3 :(得分: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);
}