Jakob Busk Sørensen
Jakob Busk Sørensen

Reputation: 6091

Show validation text in separate control

I have a property, bound to a TextBox, which is only allowed to be a positive number (i.e. x > 0). Inspired by this post I have decided to implement this using IDataErrorInfo interface.

Following the instructions in that post, I can get a tool-tip warning to show, if the input cannot be validated. But I would like to have the validation warning shown in a seperate TextBlock, just below the input TextBox.

XAML:

<!-- ValidatingControl Style -->
<Style TargetType="{x:Type FrameworkElement}" x:Key="ValidatingControl">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="True">
            <Setter Property="ToolTip" Value="{Binding 
                Path=(Validation.Errors)[0].ErrorContent, 
                RelativeSource={x:Static RelativeSource.Self}}" />  
        </Trigger>
    </Style.Triggers>
</Style>

(...)

<TextBlock Text="Product price:" />
<TextBox
    Name="Price"
    DataContext="{Binding SelectedProduct}"
    Style="{StaticResource ValidatingControl}"
    Text="{Binding Path=Price, 
            StringFormat=N0, 
            ConverterCulture=da-DK, 
            Mode=TwoWay,
            ValidatesOnDataErrors=True}"/>

<!-- This should contain validation warning -->
<TextBlock Margin="0 0 0 10" />

Binding property (C#):

public class ProductModel : IDataErrorInfo
{

    public decimal Price { get; set; }

    (...)

    // Implementation of IDataErrorInfo
    string IDataErrorInfo.Error
    {
        get { return null; }
    }

    string IDataErrorInfo.this[string columnName]
    {
        get
    {
            if (columnName == "Price")
            {
                // Validate property and return a string if there is an error
                if (Price < 0)
                    return "Cannot be negative.";
            }

            // If there's no error, null gets returned
            return null;
        }
    }
}

Upvotes: 0

Views: 106

Answers (2)

mm8
mm8

Reputation: 169280

You need to define the indexer as public property, implement the INotifyPropertyChanged interface and raise a change notification for the indexer when the Price property is set. This should work:

public class ProductModel : IDataErrorInfo, INotifyPropertyChanged
{
    private decimal _price;
    public decimal Price
    {
        get { return _price; }
        set
        {
            _price = value;
            NotifyPropertyChanged();
            NotifyPropertyChanged("Item[]");
        }
    }

    string IDataErrorInfo.Error
    {
        get { return null; }
    }

    public string this[string columnName]
    {
        get
        {
            if (columnName == "Price")
            {
                // Validate property and return a string if there is an error
                if (Price < 0)
                    return "Cannot be negative.";
            }

            // If there's no error, null gets returned
            return null;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

XAML:

<!-- This should contain validation warning -->
<TextBlock DataContext="{Binding SelectedProduct}" Margin="0 0 0 10" Text="{Binding [Price]}" />

Upvotes: 1

Gor Rustamyan
Gor Rustamyan

Reputation: 930

You can bind your textblock to IDataError interfaces indexer property.

Here modified code

    <StackPanel>
        <TextBlock Text="Product price:" />
        <TextBox  Name="Price" Text="{Binding Path=Price, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnDataErrors=True}"/>

        <!-- This should contain validation warning -->
        <TextBlock Margin="0 0 0 10" Text="{Binding [Price]}" />
    </StackPanel>

Also you need to do some modifications to your view model.

    class ViewModel : IDataErrorInfo, INotifyPropertyChanged
    {
        decimal _price;
        public decimal Price
        {
            get => _price;
            set
            {
                _price = value;
                RaisePropertyChanged(nameof(Price));
                RaisePropertyChanged("Item[]");
            }
        }

        // Implementation of IDataErrorInfo
        string IDataErrorInfo.Error
        {
            get { return null; }
        }

        public string this[string columnName]
        {
            get
            {
                if (columnName == "Price")
                {
                    // Validate property and return a string if there is an error
                    if (Price < 0)
                        return "Cannot be negative.";
                }

                // If there's no error, null gets returned
                return null;
            }
        }

        void RaisePropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

Note, that indexer property for IDataErrorInfo interface is implemented implicitly, otherwise WPF system for some reason can't bind to it. Also take a look to the INotifyPropertyChanged interface implementation for Price property, especially on the RaisePropertyChanged("Item[]"); line. Without this line WPF's system binding system will not know, if there is an error.

Upvotes: 1

Related Questions