Reputation: 22008
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
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:
Not that I wrote this answer based on dymanoid's answer which is accurate based upon given information.
Upvotes: 1
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 DataTemplate
s 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".
Upvotes: 2