views:

29

answers:

3

I'm trying to change the way a DataGridCell appears when it has error content. By default, it has a white background and a red border when it encounters an error. However, it seems that my attempts to set the Validation.ErrorTemplate of this class are being ignored, as are my Triggers on the Validation.HasError property. All of the code necessary to reproduce the problem is located below; I have been completely unable to get WPF to apply any kind of stylings to the cell on error. Does anyone see the problem here or have a potential workaround?

APP.XAML

<Application.Resources>
        <ControlTemplate x:Key="ErrorTemplate">
            <DockPanel>
                <TextBlock Text="Busted!"/>
                <AdornedElementPlaceholder/>
            </DockPanel>
        </ControlTemplate>
        <Style x:Key="{x:Type WpfToolkit:DataGridCell}" TargetType="{x:Type WpfToolkit:DataGridCell}">
            <Style.Setters>
                <Setter Property="Validation.ErrorTemplate" Value="{StaticResource ErrorTemplate}"/>
                <Setter Property="Background" Value="Green"/>
            </Style.Setters>
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="Background" Value="Red"/>
                    <Setter Property="ToolTip" Value="Doh!"/>
                </Trigger>
            </Style.Triggers>

        </Style>
    </Application.Resources>

MainWindow.xaml

<Grid>

        <WpfToolkit:DataGrid ItemsSource="{Binding Path=MyItems, ValidatesOnDataErrors=True}" AutoGenerateColumns="False">
            <WpfToolkit:DataGrid.Columns>
                <WpfToolkit:DataGridTextColumn Header="Wheel Count" Binding="{Binding WheelCount, ValidatesOnDataErrors=True}"/>
                <WpfToolkit:DataGridTextColumn Header="Car Colour" Binding="{Binding Color, ValidatesOnDataErrors=True}"/>
            </WpfToolkit:DataGrid.Columns>
        </WpfToolkit:DataGrid>
    </Grid>

The class being bound to is

Car.cs

class Car : IDataErrorInfo, INotifyPropertyChanged
    {
        public Car(int numWheels, string Color)
        {
            _color = Color;
            _wheelCount = numWheels;
        }

        private string _color;
        public string Color
        {
            get
            {
                return _color;
            }
            set
            {
                _color = value;
                if (value.Length > 10)
                {
                    //Warning
                }
                if (value.Length > 20)
                {
                    //Error
                }
                PropChanged("Color");

            }
        }

        private int _wheelCount;
        public int WheelCount
        {
            get
            {
                return _wheelCount;
            }
            set
            {
                _wheelCount = value;
                if (value != 4)
                {
                    AddError("WheelCount", "There should be 4 wheels on the car!");
                }
                else
                    this.ClearError("WheelCount");
                PropChanged("WheelCount");

            }
        }

        public void AddError(string propName, string errorMessage)
        {
            if (myErrors.ContainsKey(propName))
                myErrors.Remove(propName);
            myErrors.Add(propName, errorMessage);
            PropChanged(propName);
            PropChanged("Error");
        }

        public void ClearError(string propName)
        {
            if (myErrors.ContainsKey(propName))
                myErrors.Remove(propName);
        }

        private Dictionary<string, string> myErrors = new Dictionary<string, string>();

        public string Error
        {
            get
            {
                StringBuilder myError = new StringBuilder("Errors");
                foreach (KeyValuePair<string,string> currentErr in myErrors)
                {
                    myError.AppendLine(currentErr.Key + ":" + currentErr.Value);
                }
                return myError.ToString();
            }
        }

        public string this[string columnName]
        {
            get
            {
                if (!myErrors.ContainsKey(columnName))
                    return null;
                else
                    return myErrors[columnName];
            }
        }

        public void PropChanged(string propName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

The "ViewModel" for the view is CarVM:

CarVM.cs

 class CarVM
    {
        public CarVM()
        {
            for (int currentCar = 0; currentCar < 20; currentCar++)
            {
                myCars.Add(new Car(4, "Red"));
            }
            MyItems = (CollectionView)CollectionViewSource.GetDefaultView(myCars);
        }
        private List<Car> myCars = new List<Car>();
        public CollectionView MyItems
        {
            get;
            private set;
        }
    }

And the code in the window is very simple:

MainWindow.Xaml.Cs

 public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new CarVM();
        }
    }
A: 

Instead of AddError could you please try throw new ArgumentException like that to confirm?

Does this following condition fails? Not in both wheel count and color

        for (int currentCar = 0; currentCar < 20; currentCar++)
        {
            myCars.Add(new Car(4, "Red"));
        }

HTH

Avatar
I know the error condition is being applied, because the borders turn red. What's not being applied is my custom template.
GWLlosa
@GWLlosa in the actual code could you please check it has Key?`x:Key="{x:Type WpfToolkit:DataGridCell}"` It should not have x:Key. Else, as you know the DataGridCell's won't be able to get this style applied. You have specified `TargetType` that should be enough I guess. If you specified `x:Key` remove it and check.
Avatar
I do have that key. I was under the impression that having the key set to the type would cause it to be applied be default to all DataGridCells. I will try it without the key.
GWLlosa
A: 

That is because there is no error ever generated!! Errors will be added when you assign to Color & WheelCount, and not _color & _wheelCount. You are never assigning anything to these properties.

Change your constructor to this:

public Car(int numWheels, string color)
{
    Color = color;
    WheelCount = numWheels;
}

In case, if you don't even see the initial green color than the only reason I can think of is the Build Action of your App.xaml file. Make sure it is ApplicationDefinition.

Yogesh
Things are assigned to those properties via the user interface; they are bound to the data grid, which is editable.
GWLlosa
+1  A: 

DataGrid columns are tricky to style, as the DataContext is not what you expect at the time it is created and the style is applied. See this article, in particular, "Use DataGridColumn.ElementStyle and DataGridColumn.EditingElementStyle to set properties on a cell’s Content."

Dan Bryant
Those things apply to the DataGridColumn style; what if I want to change the appearance of only one cell?
GWLlosa
I'm not sure if there's a way to style individual cells, beyond the Editing and Non-Editing state for all cells in a column. The style applies to cells, but all cells in a column are styled the same way.
Dan Bryant
The inability to style an individual DataGridCell is disappointing, but its good to know.
GWLlosa