WPF DataGrid StyleSelector for Rows

2019-09-10 01:13发布

问题:

I'm trying to use an ItemContainerStyleSelector to display different row styles in a datagrid, based on the type of the object defining the row (ItemsSource is a collection of IGridItems, there are GridItem and GridSeparator which should get different styles). my problem was, that the SelectStyle of my StyleSelector was never called. Now I found out (here) the problem could be an inherited style (MahApps Metro library redefines the default styles of all standard controls), which probably already sets an ItemContainerStyle.

So now my question is: Is there a way to still use my StyleSelector, so that I have the inherited style as base for the selected styles? And if not, how do I achieve a different style just for some rows based on their object type?

EDIT:
Manually setting the ItemContainerStyle to null didn't have an effect, SelectStyle of my StyleSelector is still never called.

EDIT2:
Since I don't get System.Windows.Data Error: 24 : Both 'ItemContainerStyle' and 'ItemContainerStyleSelector' are set; 'ItemContainerStyleSelector' will be ignored. like Grx70 asked, I assume that the ItemContainerStyle is not the problem, like I initially thought.

jstreet pointed out, that it's related to MahApps.Metro, though... (see his comment)


My current implementation:

<DataGrid ItemsSource="{Binding Items}" ItemContainerStyleSelector="{StaticResource StyleSelector}">

The Syle selector:

public class GridRowStyleSelector : StyleSelector
{
    private readonly ResourceDictionary _dictionary;

    public GridRowStyleSelector()
    {
        _dictionary = new ResourceDictionary
        {
            Source = new Uri(@"pack://application:,,,/myApp;component/View/GridResources.xaml")
        };
    }

    public override Style SelectStyle(object item, DependencyObject container)
    {
        string name = item?.GetType().Name;
        if (name != null && _dictionary.Contains(name))
        {
            return (Style)_dictionary[name];
        }
        return null;
    }
}

GridResources.xaml with test values:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="GridItem" TargetType="DataGridRow">
        <Setter Property="BorderThickness" Value="3"/>
    </Style>
    <Style x:Key="GridSeparator"  TargetType="DataGridRow">
        <Setter Property="BorderBrush" Value="Red"/>
    </Style>
</ResourceDictionary>

回答1:

I think I've found the culprit. Turns out that the proper way to handle row styles in DataGrid is through RowStyle and RowStyleSelector properties and not ItemContainerStyle and ItemContainerStyleSelector. They work, but only until you explicitly use RowStyle or RowStyleSelector. That's exactly what MahApps Metro does - it sets RowStyle value (I believe through overriding DataGrid default style). Then I think ItemContainerStyle is set internally by DataGrid (some testing revealed that ItemContainerStyle was set despite explicitly setting it to null).

So to sum up, this should do the trick for you:

<DataGrid ItemsSource="{Binding Items}"
          RowStyle="{x:Null}"
          RowStyleSelector="{StaticResource StyleSelector}">
    (...)
</DataGrid>

Also, to modify MahApps row style rather than discard it completely you should base your styles on the MahApps one:

<Style x:Key="GridItem" TargetType="DataGridRow"
       BasedOn="{StaticResource MetroDataGridRow}">
    <Setter Property="BorderThickness" Value="3"/>
</Style>
<Style x:Key="GridSeparator" TargetType="DataGridRow"
       BasedOn="{StaticResource MetroDataGridRow}">
    <Setter Property="BorderBrush" Value="Red"/>
</Style>


回答2:

You can override the default ItemContainerStyle by explicitly setting it to null:

<DataGrid ItemsSource="{Binding Items}"
          ItemContainerStyle="{x:Null}"
          ItemContainerStyleSelector="{StaticResource StyleSelector}">


回答3:

The constructor of the GridRowStyleSelector class is static and private. Try removing the 'static' keywords from this class, and make the constructor public.