Sinatr
Sinatr

Reputation: 22008

Vista style ListView (GridViewRowPresenter)

I'd like to display data in ListView with different rows (different style and different content). But as soon as I try to apply a style (which suppose to change nothing) selection stops working and it's not anymore Vista style.

What am I doing wrong? Perhaps wrong approach?

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

Upvotes: 0

Views: 872

Answers (2)

Bizhan
Bizhan

Reputation: 17145

I try to keep it as general as possible so I define a simple enum for different types that each row can take:

public enum RowType { Bool, Int32 }

Now I use it in the view model for row:

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

Now that the view model is ready I create a simple TemplateSelector which responds to RowType of the given row:

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

And I use it in Xaml like this:

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

This is the result:

enter image description here

Not that I wrote this answer based on dymanoid's answer which is accurate based upon given information.

Upvotes: 1

dymanoid
dymanoid

Reputation: 15227

You shouldn't change the ControlTemplate of the ListViewItem, if you want to display data in a ListView with different rows (different style and different content).

Use DataTemplate instead. You can style your rows according to the bound data type.

Assuming you have a view model like this:

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

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

Then, just define the DataTemplates for your items that you wish to display in the list view:

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

With this approach, you will get the list view rows that represent your data as you wish, retaining the core ListView functionality and "Vista style".

List view

Upvotes: 2

Related Questions