Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Silverlight, how do I find the first field bound to a model that has an error so that I can give it focus?

Greetings

I have a Silverlight form bound to a model object which implements INotifyDataErrorInfo and does validation when you click the save button. If some of the properties on the model come back invalid, Silverlight will automatically highlight the bound input field.

Is there a way to set the focus to the first invalid field?

UPDATE: Is there even a way to see if an input field is in that invalid display state? If I can detect that, I can loop through my fields and set the focus manually.

Thanks, Matthew

like image 547
Matthew Avatar asked Jun 06 '11 21:06

Matthew


2 Answers

You could use a ValidationSummary in your view to display all validation errors your model raised. When you click on an error in the ValidationSummary the control which caused the validation error will be focused.

An example of the ValidationSummary can be found on the samples of the Silverlight Toolkit.

Until now I didn´t use the ValidationSummary in any application, so i cannot provide you any informations about usage or "how to use", but maybe this will help you

like image 197
Jehof Avatar answered Oct 11 '22 22:10

Jehof


I've implemented this behavior. First you need to subscribe to your ViewModel ErrorsChanged and PropertyChanged methods. I am doing this in my constructor:

    /// <summary>
    /// Initializes new instance of the View class.
    /// </summary>
    public View(ViewModel viewModel)
    {
        if (viewModel == null)
            throw new ArgumentNullException("viewModel");

        // Initialize the control
        InitializeComponent();  // exception

        // Set view model to data context.
        DataContext = viewModel;

        viewModel.PropertyChanged += new PropertyChangedEventHandler(_ViewModelPropertyChanged);
        viewModel.ErrorsChanged += new EventHandler<DataErrorsChangedEventArgs>(_ViewModelErrorsChanged);
    }

Then write handlers for this events:

    /// <summary>
    /// If model errors has changed and model still have errors set flag to true, 
    /// if we dont have errors - set flag to false.
    /// </summary>
    /// <param name="sender">Ignored.</param>
    /// <param name="e">Ignored.</param>
    private void _ViewModelErrorsChanged(object sender, DataErrorsChangedEventArgs e)
    {
        if ((this.DataContext as INotifyDataErrorInfo).HasErrors)
            _hasErrorsRecentlyChanged = true;
        else
            _hasErrorsRecentlyChanged = false;
    }

    /// <summary>
    /// Iterate over view model visual childrens.
    /// </summary>
    /// <param name="sender">Ignored.</param>
    /// <param name="e">Ignored.</param>
    private void _ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if ((this.DataContext as INotifyDataErrorInfo).HasErrors)
            _LoopThroughControls(this);
    }

And finally add method:

    /// <summary>
    /// If we have error and we haven't already set focus - set focus to first control with error.
    /// </summary>
    /// <remarks>Recursive.</remarks>
    /// <param name="parent">Parent element.</param>
    private void _LoopThroughControls(UIElement parent)
    {
        // Check that we have error and we haven't already set focus
        if (!_hasErrorsRecentlyChanged)
            return;

        int count = VisualTreeHelper.GetChildrenCount(parent);

        // VisualTreeHelper.GetChildrenCount for TabControl will always return 0, so we need to 
        // do this branch of code.
        if (parent.GetType().Equals(typeof(TabControl)))
        {
            TabControl tabContainer = ((TabControl)parent);
            foreach (TabItem tabItem in tabContainer.Items)
            {
                if (tabItem.Content == null)
                    continue;

                _LoopThroughControls(tabItem.Content as UIElement);
            }
        }

        // If element has childs.
        if (count > 0)
        {
            for (int i = 0; i < count; i++)
            {
                UIElement child = (UIElement)VisualTreeHelper.GetChild(parent, i);

                if (child is System.Windows.Controls.Control)
                {
                    var control = (System.Windows.Controls.Control)child;

                    // If control have error - we found first control, set focus to it and 
                    // set flag to false.
                    if ((bool)control.GetValue(Validation.HasErrorProperty))
                    {
                        _hasErrorsRecentlyChanged = false;
                        control.Focus();
                        return;
                    }
                }

                _LoopThroughControls(child);
            }
        }
    }
like image 26
Seekeer Avatar answered Oct 11 '22 22:10

Seekeer