Devid
Devid

Reputation: 2003

GridView validation marks whole row as invalid instead of just the cell

The Problem

I have implemented validation with INotifyDataErrorInfo interface. My Problem is that instead of showing only the cell in the Grid as invalid and marking it with red color I get the whole row marked with red. When the GridView has lot of rows, each one having invalid cells, it doesn't look good and is hard to see what is invalid. I am using the RadGridView from the WPF Telerik library.

How can I mark only the cell red and not the whole row ?

Visualization of the problem

In the below image if a cell has the value 'String-1.2' it is invalid. The cell is shown as invalid but the whole row is marked with red color instead of just the cell. enter image description here

Source Code

This class represents a cell in my GridView:

public class ShowQueueCellValue : INotifyPropertyChanged, INotifyDataErrorInfo
{
    ...
     public object Value
    {
        get => this.value;
        set
        {
            bool hasChanged = value != this.value;
            this.value = value;
            ...
        }
    }
     public List<ShowQueueCellValue> GridRow { get; set; }

     public string ErrorMessage
    {
        get => this.errorMessage;
        set
        {
            this.errorMessage = value;
            IsValid = string.IsNullOrWhiteSpace(value) || value == "0";
        }
    }

    public bool IsValid
    {
        get => this.isValid;
        set
        {
            if (value != this.isValid)
            {
                this.isValid = value;
                OnErrorsChanged(nameof(Value));
                this.viewModel.OkCommand.RaiseCanExecuteChanged();
            }
        }
    }
    public IEnumerable GetErrors(string propertyName)
    {
        if (!IsValid && propertyName == nameof(Value))
        {
            yield return ErrorMessage;
        }
    }

    public bool HasErrors => !IsValid;

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public void OnErrorsChanged(string propertyName)
    {
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Upvotes: 3

Views: 501

Answers (1)

Joel Priddy
Joel Priddy

Reputation: 441

While I realize this is an old thread, it is the single one I have been able to locate while trying to solve this exact same problem. I haven't found anywhere else where it is addressed. SO, since my coworker and I found a way to at least stop all the cells from coloring red, I will post that solution.

In my resource dictionary I defined the following styles, which is mostly just a copy of the validation styling from Telerik (I'll show the changed part at the end):

<ControlTemplate x:Key="NoValidationBackground" TargetType="telerik:GridViewCell">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="SelectionStates">
                <VisualState x:Name="Unselected"/>
                <VisualState x:Name="Selected">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background_Selected" Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <Visibility>Visible</Visibility>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="SelectedUnfocused">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="Background_SelectedUnfocused" Storyboard.TargetProperty="(UIElement.Visibility)">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <Visibility>Visible</Visibility>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
            <VisualStateGroup x:Name="CommonStates">
                <VisualState x:Name="Normal"/>
                <VisualState x:Name="Current">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background_Current" Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <Visibility>Visible</Visibility>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="MouseOver">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background_Over" Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <Visibility>Visible</Visibility>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
            <VisualStateGroup x:Name="EditingStates">
                <VisualState x:Name="Edited">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Margin">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <Thickness>0</Thickness>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="VerticalAlignment">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <VerticalAlignment>Stretch</VerticalAlignment>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_CellBorder" Storyboard.TargetProperty="Background">
                            <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{telerik:Office2013Resource ResourceKey=MainBrush}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="Display"/>
            </VisualStateGroup>
            <VisualStateGroup x:Name="DisabledStates">
                <VisualState x:Name="Enabled"/>
                <VisualState x:Name="Disabled">
                    <Storyboard>
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="PART_ContentPresenter" Storyboard.TargetProperty="Opacity">
                            <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="{telerik:Office2013Resource ResourceKey=DisabledOpacity}"/>
                        </DoubleAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background_Disabled" Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <Visibility>Visible</Visibility>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
            <VisualStateGroup x:Name="ValueStates">
                <VisualState x:Name="CellValid"/>
                <VisualState x:Name="CellInvalid">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background_Invalid" Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <Visibility>Visible</Visibility>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="CellInvalidUnfocused">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background_Invalid_Unfocused" Storyboard.TargetProperty="Visibility">
                            <DiscreteObjectKeyFrame KeyTime="0">
                                <DiscreteObjectKeyFrame.Value>
                                    <Visibility>Visible</Visibility>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
            <VisualStateGroup x:Name="HighlightStates">
                <VisualState x:Name="NotHighlighted"/>
                <VisualState x:Name="Highlighted">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="PART_CellBorder" Storyboard.TargetProperty="Background">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource GridViewCell_HightlightedBrush}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Border x:Name="PART_CellBorder"
                Background="{Binding Background, RelativeSource={RelativeSource TemplatedParent}}"
                BorderBrush="{TemplateBinding VerticalGridLinesBrush}"
                BorderThickness="{Binding VerticalGridLinesWidth, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource GridLineWidthToThicknessConverter}, ConverterParameter=Right}"
                Margin="0 0 0 1"/>
        <Border x:Name="Background_SelectedUnfocused"
                Grid.Column="2"
                Grid.ColumnSpan="2"
                Background="{telerik:Office2013Resource ResourceKey=AccentMainBrush}"
                Opacity="0.28"
                Visibility="Collapsed"/>
        <Border x:Name="Background_Over"
                BorderBrush="{TemplateBinding VerticalGridLinesBrush}"
                Margin="0 0 0 1"
                BorderThickness="{Binding VerticalGridLinesWidth, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource GridLineWidthToThicknessConverter}, ConverterParameter=Right}"
                Grid.Column="2"
                Grid.ColumnSpan="2"
                Visibility="Collapsed"
                Background="{TemplateBinding MouseOverBackground}"/>
        <Border x:Name="Background_Selected"
                BorderBrush="{TemplateBinding VerticalGridLinesBrush}"
                Margin="0 0 0 1"
                BorderThickness="{Binding VerticalGridLinesWidth, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource GridLineWidthToThicknessConverter}, ConverterParameter=Right}"
                Grid.Column="2"
                Grid.ColumnSpan="2"
                Visibility="Collapsed"
                Background="{TemplateBinding SelectedBackground}"/>
        <Border x:Name="Background_Invalid_Unfocused" Margin="0 0 1 1" Background="White" Grid.Column="2" Grid.ColumnSpan="2"/>
        <Border x:Name="Background_Current"
                BorderThickness="1"
                Grid.Column="2"
                Grid.ColumnSpan="2"
                Visibility="Collapsed"
                Margin="0 0 1 1"
                BorderBrush="{TemplateBinding CurrentBorderBrush}"/>
        <!--The background property of this item controls the background of any cell that contains an invalid property value-->
        <Border x:Name="Background_Invalid"
                Background="{telerik:Office2013Resource ResourceKey=MainBrush}"
                BorderBrush="{telerik:Office2013Resource ResourceKey=ValidationBrush}"
                BorderThickness="1"
                Grid.Column="2"
                Grid.ColumnSpan="2"
                Visibility="Collapsed">
            <ToolTipService.ToolTip>
                <ToolTip x:Name="validationTooltip" Placement="Right" Content="{TemplateBinding Errors}" Template="{StaticResource GridViewCell_ValidationToolTipTemplate}"/>
            </ToolTipService.ToolTip>
            <!--This item controls the corner triangle in a invalid cell which is not selected-->
            <Grid Height="22" HorizontalAlignment="Right" Margin="0 -1 -1 0" Width="22" VerticalAlignment="Top">
                <Path
                        Data="M0,0 L10,0 10,10 z"
                        Fill="{telerik:Office2013Resource ResourceKey=ValidationBrush}"
                        HorizontalAlignment="Right"
                        VerticalAlignment="Top"
                        Width="10"
                        Height="10"
                        Stretch="Fill"/>
            </Grid>
        </Border>
        <Border x:Name="Background_Disabled" Background="{telerik:Office2013Resource ResourceKey=MainBrush}" Opacity="0.5" Grid.Column="2" Grid.ColumnSpan="2" Visibility="Collapsed"/>
        <ContentPresenter x:Name="PART_ContentPresenter"
                Margin="{TemplateBinding Padding}"
                Content="{TemplateBinding Content}"
                ContentTemplate="{TemplateBinding ContentTemplate}"
                VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
    </Grid>
    <ControlTemplate.Triggers>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=ParentRow.HorizontalGridLinesWidth}" Value="0">
            <Setter TargetName="PART_CellBorder" Property="Margin" Value="0"/>
            <Setter TargetName="Background_Current" Property="Margin" Value="0 0 1 0"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=ParentRow.DetailsVisibility}" Value="Visible">
            <Setter TargetName="PART_CellBorder" Property="Margin" Value="0"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=ParentRow.VerticalGridLinesWidth}" Value="0">
            <Setter TargetName="Background_Current" Property="Margin" Value="0 0 0 1"/>
        </DataTrigger>
        <MultiDataTrigger>
            <MultiDataTrigger.Conditions>
                <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=ParentRow.HorizontalGridLinesWidth}" Value="0"/>
                <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=ParentRow.VerticalGridLinesWidth}" Value="0"/>
            </MultiDataTrigger.Conditions>
            <Setter TargetName="Background_Current" Property="Margin" Value="0"/>
        </MultiDataTrigger>
    </ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="CellNoValidationBackground" TargetType="telerik:GridViewCell">
    <Setter Property="Template" Value="{StaticResource NoValidationBackground}"/>
    <Setter Property="Padding" Value="5 0"/>
    <Setter Property="BorderThickness" Value="0 0 1 0"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="SnapsToDevicePixels" Value="True"/>
    <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisualStyle}"/>
    <Setter Property="SelectedBackground" Value="{telerik:Office2013Resource ResourceKey=EffectAccentHighBrush}"/>
    <Setter Property="CurrentBorderBrush" Value="{telerik:Office2013Resource ResourceKey=AccentMainBrush}"/>
    <Setter Property="MouseOverBackground" Value="{telerik:Office2013Resource ResourceKey=EffectAccentLowBrush}"/>
</Style>
<Style TargetType="telerik:GridViewCell" BasedOn="{StaticResource CellNoValidationBackground}"/>

The piece that I changed is this line:

<Border x:Name="Background_Invalid_Unfocused" Margin="0 0 1 1" Background="White" Grid.Column="2" Grid.ColumnSpan="2"/>

Previously, it had a Visibility property set to collapsed. This would have the behavior that an entire row would show the pink color unless you were editing it, where it would collapse the pink color to show white. Clearly, you can use whatever color you want in there.

Upvotes: 1

Related Questions