0
votes

I have an WPF MVVM application. In main WPF Window I have a WPF Datagrid Toolkit. This datagrid has some columns. Depending on some conditions (properties bound on view model) a color is applied to the entire row. Additionally there are two special columns. These two columns are of type DataGridTextColumn and are called Date1 and Date2. If Date1 value is greater than Date2 value, Date1 and Date2 cells should be both colored with Orange Red color. Date1 and Date2 which are bound to MyDate1 and MyDate2 view model properties respectively, are of type DateTime.

My problem is: First, when datagrid is loaded with data, rows and Date1 and Date2 columns are colored correctly. Some rows appear with Date1 and Date2 colored with Orange Red color since Date1 > Date2. Then, If I scroll down using vertical scrollbar the datagrid and then I scroll up again to the first datagrid row, I note that now more rows (Date1 and Date2 columns) appear incorrectly colored as Orange Red despite Date1 <= Date2.

What is happening?

View:

<Window x:Name="MainWindow" x:Class="My.Apps.WPF.TestApp.wMain"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="http://schemas.microsoft.com/wpf/2008/toolkit" 
    xmlns:classes="clr-namespace:My.Apps.WPF.TestApp.Classes"
    xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"        
    WindowState="Maximized">


    <Grid x:Name="MyGrid" HorizontalAlignment="Stretch">
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
        </Grid.RowDefinitions>

        <my:DataGrid  Grid.Row="1" Name="MyDg"                         
                  AutoGenerateColumns="False"
                  ItemsSource="{Binding Path=MyListOfItems}" 
                  SelectedItem="{Binding Path=MySelectedItem}" 
                  VerticalAlignment="Stretch" IsReadOnly="True" 
                  SelectionMode="Single" ColumnWidth="*"                                   
                  SelectionChanged="MyDg_SelectionChanged"
                  Width="{Binding Path=ActualWidth, ElementName=MyGrid}">

                <my:DataGrid.Resources>
                    <classes:BindingProxy x:Key="proxy" Data="{Binding}" />
                    <Style x:Key="MyDataGridCellStyle" TargetType="{x:Type my:DataGridCell}" BasedOn="{StaticResource {x:Type my:DataGridCell}}">
                        <!-- If Date1 > Date2 apply background color -->
                        <Setter Property="Background">
                            <Setter.Value>
                                <MultiBinding Converter="{StaticResource CellDateColorConverter}">
                                    <Binding Path="MyDate1"/> <!-- Date1 value -->
                                    <Binding Path="MyDate2"/> <!-- Date2 value -->
                                </MultiBinding>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </my:DataGrid.Resources>

                <my:DataGrid.RowStyle>
                    <!-- Style for entire datagrid row -->
                    <Style TargetType="my:DataGridRow">
                        <Style.Triggers>
                            <MultiDataTrigger>
                                <MultiDataTrigger.Conditions>
                                    <Condition Binding="{Binding Path=GroupID, Converter={StaticResource IdGroupConverter}}" Value="True" />
                                    <Condition Binding="{Binding Path=MyFlag}" Value="0" />
                                    <Condition Binding="{Binding Path=StatusFilter, Converter={StaticResource StatusFilterConverter}}" Value="True"/>
                                </MultiDataTrigger.Conditions>
                                <Setter Property="Background" Value="#FFAC62"></Setter>
                            </MultiDataTrigger>

                            <DataTrigger Binding="{Binding Path=MyFlag}}" Value="1">
                                <Setter Property="Background" Value="{Binding DeniedColor}"></Setter>
                            </DataTrigger>

                            <DataTrigger Binding="{Binding Path=MyFlag}" Value="2">
                                <Setter Property="Background" Value="{Binding PendingColor}"></Setter>
                            </DataTrigger>

                        </Style.Triggers>
                    </Style>
                </my:DataGrid.RowStyle>

                 <my:DataGrid.Columns>

                        <my:DataGridTextColumn x:Name="Date1" 
                                               CellStyle="{StaticResource MyDataGridCellStyle}"
                                               Binding="{Binding Path=MyDate1, StringFormat=\{0:dd/MM/yyyy\}}" 
                                               Header="Date 1" 
                                               Width="{Binding DataComandaColWidth, Source={StaticResource DO}, Mode=TwoWay}"
                                               HeaderStyle="{DynamicResource CenterGridHeaderStyle}">

                            <my:DataGridTextColumn.ElementStyle>
                                <Style TargetType="TextBlock">
                                    <Setter Property="HorizontalAlignment" Value="Center" />
                                    <Setter Property="Margin" Value="5 0"/>                                 
                                </Style>
                            </my:DataGridTextColumn.ElementStyle>
                        </my:DataGridTextColumn>

                        <my:DataGridTextColumn x:Name="Date2" 
                                               CellStyle="{StaticResource MyDataGridCellStyle}" 
                                               Binding="{Binding Path=MyDate2, StringFormat=\{0:dd/MM/yyyy\}}" 
                                               Header="Date 2" 
                                               Width="auto" 
                                               HeaderStyle="{DynamicResource CenterGridHeaderStyle}">
                            <my:DataGridTextColumn.ElementStyle>
                                <Style TargetType="TextBlock">
                                    <Setter Property="HorizontalAlignment" Value="Center" />
                                    <Setter Property="Margin" Value="5 0"/>                                 
                                </Style>
                            </my:DataGridTextColumn.ElementStyle>
                        </my:DataGridTextColumn>                 

                 </my:DataGrid.Columns>

        </my:DataGrid>            
    </Grid>
</Window>   

Classes:

namespace My.Apps.WPF.TestApp.Classes
{
public class CellDateColorConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (values[0] is DateTime && values[1] is DateTime)
        {
            DateTime date1 = (DateTime)values[0];
            DateTime date2 = (DateTime)values[1];                

            if (date1.Date > date2.Date)
            {
                return System.Windows.Media.Brushes.OrangeRed;
            }
        }

        return System.Windows.Data.Binding.DoNothing;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException("CellDateColorConverter is a OneWay converter.");
    }
}

public class BindingProxy : Freezable
{
    #region Overrides of Freezable

    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    #endregion

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

}
1
The cell colour looks like it'll apply to an entire row. Why is the cell colouring not in with the row stuff? I think I'd also put the date comparison in the row viewmodel rather than use a converter. Your problem seems likely to be related to virtualisation and the rows are recycled. First step I would take is to simplify and rationalise what controls colours.Andy
@Andy Cell colouring is not part of the row stuff because it is not being applied to all row cells, only to some of them, in my case, 2 cells. Date comparison in the row view model? you mean through a property in view model? Anyway I do not know why rows are recycled, I do not hide anyone.Ralph
@Andy Yes, as you said containers are reused because VirtualizationMode by default is set to Recycling. Setting it to standard corrects and solve the problem. See my update and here: stackoverflow.com/questions/17133286/…Ralph

1 Answers

0
votes

I have solved by setting EnableRowVirtualization to False. It was set to True by default. Property EnableColumnVirtualization was set to False by default.

It seems like using virtualized cells is affecting to datagrid style in .NET 3.5. Bug? If so, I do not know if it has been corrected in NET 4.0 and above.

Anyway, I am worried about it because it means I cannot use virtualized cells... So If someone have any other solution, please share here.

Other users have experienced the same problem, see here

UPDATED:

Also, if you want to preserve EnableRowVirtualization to True, it can be solved by only changing Virtualization Mode by doing this:

<DataGrid VirtualizingStackPanel.VirtualizationMode="Standard" />

The problem is that VirtualizationMode by defaut is set to Recycling so this cause incorrect row colouring when scrolling as containers are reused. This is explained here as explained by Eirik in his answer.