AutoCompleteBox and INotifyDataErrorInfo in Silverlight

2.4k views Asked by At

Has anyone successfully applied the INotifyDataErrorInfo interface and bound to an AutoCompleteBox. I've tried this but I get no response. The control does not respond as other controls i.e. with a red border and a warning tooltip. It also does not make the Validation Summary control display with its error.

I have successfully set up standard TextBoxes and DatePickers and these behave perfectly as per the many examples kindly provided by people on the internet.

it would be good if there was an answer to this for the consistency of my screen, also because i would like to simply bind to the HasErrors property that comes with INotifyDataErrorInfo to enable a button when ready to save and I can't do this without extra code to check that these boxes are correct.

At the moment I treat these differently by using an MVVMLight EventToCommand binding and registering the LostFocus event.

<sdk:AutoCompleteBox x:Name="TransferTypeTextBox" SelectedItem="{Binding Path=SelectedTransferType, Mode=TwoWay, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True}" ItemsSource="{Binding Path=TransferTypes}" IsTextCompletionEnabled="True"  Grid.Row="1" Grid.Column="1" Margin="0,3" Width="238" HorizontalAlignment="Left" FontFamily="/PtrInput_Silverlight;component/Fonts/Fonts.zip#Calibri" FontSize="13.333">
      <i:Interaction.Triggers>
           <i:EventTrigger EventName="LostFocus">
               <cmd:EventToCommand Command="{Binding TransferTypeLostFocusCommand}" PassEventArgsToCommand="True"/>
           </i:EventTrigger>
      </i:Interaction.Triggers>
</sdk:AutoCompleteBox>

In the ViewModel I then cast the RoutedEventArgs.OriginalSource to a TextBox and get the text like so, preventing the user from leaving the box unless it is empty or matching an item in the box's list: -

    private void OnTransferTypeLostFocus(RoutedEventArgs e)
    {
        System.Windows.Controls.TextBox box = (System.Windows.Controls.TextBox)e.OriginalSource;

        // If user inputs text but doesn't select one item, show message.
        if (this.Ptr.TransferType == null && !string.IsNullOrEmpty(box.Text))
        {
            MessageBox.Show("That is not a valid entry for Transfer Type", "Transfer type", MessageBoxButton.OK);
            box.Focus();
        }
    }
1

There are 1 answers

3
vortexwolf On BEST ANSWER

I've tried to write as simple example as I can. My model observe changes of the property SearchText and update validation properties.

public class MainViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
    private Dictionary<string, List<string>> ErrorMessages = new Dictionary<string, List<string>>();

    public MainViewModel()
    {
        //Validation works automatically for all properties that notify about the changes
        this.PropertyChanged += new PropertyChangedEventHandler(ValidateChangedProperty); 
    }

    //Validate and call 'OnErrorChanged' for reflecting the changes in UI
    private void ValidateChangedProperty(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "HasErrors") //avoid recursion
            return;

        this.ValidateProperty(e.PropertyName);
        OnErrorsChanged(e.PropertyName);
        OnPropertyChanged("HasErrors");
    }

    //Just compare a received value with a correct value, it's a simple rule for demonstration
    public void ValidateProperty(string propertyName)
    {
        if (propertyName == "SearchText")
        {
            this.ErrorMessages.Remove(propertyName);
            if (SearchText != "Correct value")
                this.ErrorMessages.Add("SearchText", new List<string> { "Enter a correct value" });
        }

    }

    private string searchText;

    public string SearchText
    {
        get { return searchText; }
        set
        {
            searchText = value;
            OnPropertyChanged("SearchText");
        }
    }



    #region INotifyDataErrorInfo

    public IEnumerable GetErrors(string propertyName)
    {
        return this.ErrorMessages.Where(er => er.Key == propertyName).SelectMany(er => er.Value);
    }

    public bool HasErrors
    {
        get { return this.ErrorMessages.Count > 0; }
    }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };

    private void OnErrorsChanged(string propertyName)
    {
        ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
    }
    #endregion


    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    protected virtual void OnPropertyChanged(string propertyName)
    {
        this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

And xaml:

<sdk:AutoCompleteBox Text="{Binding SearchText, Mode=TwoWay}" />
<Button IsEnabled="{Binding HasErrors, Converter={StaticResource NotConverter}}" Content="Save"/>

The control has a red border and the button is disabled when the model has errors.