Vista 风格的 ListView (GridViewRowPresenter)

Vista style ListView (GridViewRowPresenter)

我想在 ListView 中用不同的行(不同的样式和不同的内容)显示数据。但是,一旦我尝试应用一种样式(假设什么都不改变),选择就会停止工作并且不再 Vista style.

我做错了什么?也许是错误的做法?

<ListView ItemsSource="{Binding A}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="B" DisplayMemberBinding="{Binding B}"/>
            ...
        </GridView>
    </ListView.View>

    <!-- commenting below block will return Vista style back -->
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListViewItem">
                        <GridViewRowPresenter Content="{TemplateBinding Content}"/>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ListView.ItemContainerStyle>
</ListView>

如果要在 ListView 中显示不同行(不同样式和不同内容)的数据,则不应更改 ListViewItemControlTemplate

改用DataTemplate。您可以根据绑定的数据类型设置行的样式。

假设您有这样的视图模型:

public class ViewModel : INotifyPropertyChanged
{
    public List<object> A { get; private set; }

    public ViewModel()
    {
        this.A = new List<object> { Brushes.BlueViolet, 42, false };
    }
}

然后,只需为您希望在列表视图中显示的项目定义 DataTemplates:

<ListView ItemsSource="{Binding A}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="B"/>
        </GridView>
    </ListView.View>
    <ListView.Resources>
        <DataTemplate DataType="{x:Type media:Brush}">
            <Rectangle Width="25" Height="25" Fill="{Binding Mode=OneWay}"/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type sys:Boolean}">
            <CheckBox IsChecked="{Binding Mode=OneWay}"/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type sys:Int32}">
            <TextBlock Text="{Binding Mode=OneWay}">
                <TextBlock.Style>
                    <Style TargetType="{x:Type TextBlock}">
                        <Setter Property="FontWeight" Value="Bold"/>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
        </DataTemplate>
    </ListView.Resources>
</ListView>

通过这种方法,您将获得代表数据的列表视图行,保留核心 ListView 功能和 "Vista style".

我尽量保持它的通用性,所以我为每一行可以采用的不同类型定义了一个简单的枚举:

public enum RowType { Bool, Int32 }

现在我在行的视图模型中使用它:

public class Row: DependencyObject
{
    //create a new row with the given value
    //automatically set Value, Info and RowType based on the param
    public Row(object val)
    {
        Value = val;
        if (val.GetType() == typeof(bool)) RowType = WpfApplication3.RowType.Bool;
        else RowType = WpfApplication3.RowType.Int32;
        Info = val.ToString() + " of type " +val.GetType().ToString();
    }

    public RowType RowType { get; set; }

    /// <summary>
    /// Gets or sets a bindable value that indicates Value
    /// </summary>
    public object Value
    {
        get { return (object)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(object), typeof(Row), new PropertyMetadata(0));

    /// <summary>
    /// Gets or sets a bindable value that indicates Info
    /// </summary>
    public string Info
    {
        get { return (string)GetValue(InfoProperty); }
        set { SetValue(InfoProperty, value); }
    }
    public static readonly DependencyProperty InfoProperty =
        DependencyProperty.Register("Info", typeof(string), typeof(Row), new PropertyMetadata(""));
}

既然视图模型已准备就绪,我将创建一个简单的 TemplateSelector,它响应给定行的 RowType:

public class Selector : DataTemplateSelector
{
    //Template for RowType==Bool
    public DataTemplate Template1 { get; set; }
    //Template for RowType==Int32
    public DataTemplate Template2 { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var row = item as Row;
        if (row == null) return null;

        switch (row.RowType)
        {
            case RowType.Bool:
                return Template1;
            case RowType.Int32:
                return Template2;
            default:
                return null;
        }
    }
}

我在 Xaml 中这样使用它:

<Window.Resources>

    <!-- selects CellTemplate for column1 (Value) based on RowType -->
    <local:Selector x:Key="valueSelector">
        <local:Selector.Template1>
            <DataTemplate>
                <CheckBox IsChecked="{Binding Value, Mode=OneWay}"/>
            </DataTemplate>
        </local:Selector.Template1>
        <local:Selector.Template2>
            <DataTemplate>
                <TextBlock Text="{Binding Value, Mode=OneWay}">
                    <TextBlock.Style>
                        <Style TargetType="{x:Type TextBlock}">
                            <Setter Property="FontWeight" Value="Bold"/>
                        </Style>
                    </TextBlock.Style>
                </TextBlock>
            </DataTemplate>
        </local:Selector.Template2>
    </local:Selector>

    <!-- selects CellTemplate for column2 (Info) based on RowType -->
    <local:Selector x:Key="infoSelector">
        <local:Selector.Template1>
            <DataTemplate>
                <Canvas Height="16">
                    <TextBlock Text="{Binding Info, Mode=OneWay}" 
                               Foreground="Blue" VerticalAlignment="Top"/>
                </Canvas>
            </DataTemplate>
        </local:Selector.Template1>
        <local:Selector.Template2>
            <DataTemplate>
                <Canvas Height="16">
                    <TextBlock Text="{Binding Info, Mode=OneWay}"
                               VerticalAlignment="Top"/>
                </Canvas>
            </DataTemplate>
        </local:Selector.Template2>
    </local:Selector>
</Window.Resources>

<ListView ItemsSource="{Binding A}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Value" 
                 CellTemplateSelector="{StaticResource valueSelector}"/>
            <GridViewColumn Header="Info" Width="0"
                 CellTemplateSelector="{StaticResource infoSelector}"/>
        </GridView>
    </ListView.View>
</ListView>

这是结果:

并不是我根据 dymanoid 的答案写了这个答案,根据给定的信息是准确的。