RIA Service Form Validation

Jun 25, 2011 at 9:45 PM

Hi Tony,

I have a page with a view-model, that contains a datagrid, and 3 buttons: new, edit, remove.  This is very similar to the "Items" demo project you posted somewhere on your blog.  If you click Edit, for example, it pops up a new ChildWindow that has its own view model.

Here's a sample of what I mean this is the callback in the code behind for the edit button click; DealerEditor is the ChildWindow that contains the form for editing.  It has its own DealerEditorViewModel.  The form contains a simple OK / Cancel button combo.  If the user fills out the form and clicks OK, we save the changes in the viewmodel.

        private void editItemButton_Click(object sender, RoutedEventArgs e)
        {
            if (model.SelectedDealer == null) return;

            DealerEditorViewModel editorViewModel =
                new DealerEditorViewModel(model.SelectedDealer);

            // Start editing
            editorViewModel.BeginEdit();

            DealerEditor editor = new DealerEditor(editorViewModel);
            editor.Closed += (s, ea) =>
            {
                if (editor.DialogResult == true)
                {
                    // Confirm changes
                    editorViewModel.EndEdit();
                    model.SaveDealerChanges();
                }
                else
                {
                    // Reject changes
                    editorViewModel.CancelEdit();
                }
            };
            editor.Show();
        }

This works fine and all, and Validation actually works b/c if I type in invalid data in a form field according to the data annotations, it highlights it in red, and shows the error message.  However, it still doesn't prevent the user from clicking OK and attempting a save even if the validation fails.  I'm struggling with trying to figure out how to do this.

I can obviously add a boolean property OkEnabled on the DealerEditorViewModel and tie the button's Enabled property to it.

private bool okEnabled;
        
        public bool OkEnabled
        {
            get { return okEnabled; }
            set { okEnabled = value; }
        }

The thing I'm not sure about is how to hook into the PropertyChanged event so that I can update the state of this boolean value.  Here's another look at the DealerEditor -- just a ChildWindow with a ViewModel class:

public partial class DealerEditor : ChildWindow
    {
        public DealerEditor()
        {
            InitializeComponent();
        }

        public DealerEditor(DealerEditorViewModel viewModel)
            : this()
        {
            DataContext = viewModel;
        }

...and here's the DealerEditorViewModel -- it is here that I want to trap on PropertyChanged events fired from the Model, and then check if there are any ValidationErrors -- if there are, then I update the state of the OkEnabled property to false, else true.

public class DealerEditorViewModel : ViewModelDetailBase<DealerEditorViewModel, Dealer>
    {
        #region Initialization and Cleanup

        public DealerEditorViewModel() { }

        public DealerEditorViewModel(Dealer model)
        {
            base.Model = model;
        }

-- can I somehow hook into the base.Model.PropertyChanged event -- would that be the best way to do this?

Jun 25, 2011 at 10:01 PM

A quick follow-up, I tried doing this -- at least it gives you a flavor of what I'm trying to accomplish here, but it does not work:

public DealerEditorViewModel(Dealer model)
        {
            base.Model = model;
            base.Model.PropertyChanged += (s, ea) =>
                {
                    if (Model.ValidationErrors.Count > 0)
                    {
                        OkEnabled = false;
                    }
                    else { OkEnabled = true; }
                };
        }

The anonymous handler is never reached with this code -- do you have any suggestion for hooking into this to accomplish my end goal?  

Thanks in advance!

Jun 29, 2011 at 7:09 PM

@zenocon: Sorry for my delay in responding, but I wanted to build a good demo for doing validation with Simple MVVM Toolkit and WCF RIA Services.  What you need to do is add a CanSave property to the detail ViewModel and bind the IsEnabled property of the OK button on the detail View to it.  The CanSave property returns false if the model has validation errors.

// Can save if there are no validation errors
public bool CanSave
{
    get
    { 
        return !base.Model.HasValidationErrors; 
    }
}

The trick is to ask the View to check this property when a property on the model changes.  For that, you'll override IEditableObject methods on the ViewModel and call NotifyPropertyChanged when a property on the model changes.

private void OnModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    NotifyPropertyChanged(vm => vm.CanSave);
}

protected override void OnBeginEdit()
{
    base.Model.PropertyChanged += OnModelPropertyChanged;
    base.OnBeginEdit();
}

protected override void OnCancelEdit()
{
    base.Model.PropertyChanged -= OnModelPropertyChanged;
    base.OnCancelEdit();
}

protected override void OnEndEdit()
{
    base.Model.PropertyChanged -= OnModelPropertyChanged;
    base.OnEndEdit();
}

I have created a sample that demonstrates this.  When you edit an item, the model dialog will appear in edit mode.  If you were to clear out the item name, which is required, the validation error would appear and the OK button would be disabled.  When you add a non-blank item name, the validation error disappears and the OK becomes enabled. To make this sample work, you should attach the SampleDb database in the Data folder to the SQL Express instance.

http://tonysneed.com/simplemvvm/SimpleMvvmRiaValidation.zip

Cheers,

Tony