tags:

views:

181

answers:

3

I've got a small test WPF MVVM application working in which a view allows the user to change the first or last names of customers and the full name automatically changes, so communication is going from M-to-MV-to-V and back, everything is fully decoupled, so far so good.

But now as I look to how I will begin extending this to build large applications with the MVVM pattern, I find the decoupling to be an obstacle, namely:

  • how will I do validation messages, e.g. if back in the model in the LastName setter I add code that prevents names over 50 characters from being set, how can I send a messsage to the view telling it to display a message that the name was too long?

  • in complex applications I may have dozens of views on a screen at one time, yet I understand that in MVVM each view has one and only one ViewModel assigned to it to provide it with data and behavior, so how is it that the views can interact with each other, e.g. in the above validation example, what if back in the customer model we want to inform a particular "MessageAreaView" to display the message "Last name may only contain 50 characters.", how to we communicate that up the stack to that particular view?

CustomerHeaderView.xaml (View):

<UserControl x:Class="TestMvvm444.Views.CustomerHeaderView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;
    <Grid>
        <StackPanel HorizontalAlignment="Left">
            <ItemsControl ItemsSource="{Binding Path=Customers}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <StackPanel Orientation="Horizontal">
                                <TextBox
                                Text="{Binding Path=FirstName, Mode=TwoWay}" 
                                Width="100" 
                                Margin="3 5 3 5"/>
                                <TextBox 
                                Text="{Binding Path=LastName, Mode=TwoWay}" 
                                Width="100"
                                Margin="0 5 3 5"/>
                                <TextBlock 
                                Text="{Binding Path=FullName, Mode=OneWay}" 
                                Margin="0 5 3 5"/>
                            </StackPanel>
                        </StackPanel>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </StackPanel>
    </Grid>
</UserControl>

Customer.cs (Model):

using System.ComponentModel;

namespace TestMvvm444.Model
{
    class Customer : INotifyPropertyChanged
    {

        public int ID { get; set; }
        public int NumberOfContracts { get; set; }

        private string firstName;
        private string lastName;

        public string FirstName
        {
            get { return firstName; }
            set
            {
                if (firstName != value)
                {
                    firstName = value;
                    RaisePropertyChanged("FirstName");
                    RaisePropertyChanged("FullName");

                }
            }
        }

        public string LastName
        {
            get { return lastName; }
            set
            {
                if (lastName != value)
                {
                    lastName = value;
                    RaisePropertyChanged("LastName");
                    RaisePropertyChanged("FullName");
                }
            }
        }

        public string FullName
        {
            get { return firstName + " " + lastName; }
        }



        #region PropertChanged Block
        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
        #endregion
    }
}
+4  A: 

For validation, have your view model implement IDataErrorInfo. As for communication between views, don't be afraid to write UI services (eg. a message service that allows and view model to contribute messages that will be displayed somewhere in the UI). Or if there is a hard relationship between view models (eg. one view model owns another) then the owning view model can hold a reference to the child view model.

HTH, Kent

Kent Boogaart
+2  A: 

A really simple way to add validation messages is to use binding.

Add an notifable property to your view model that defines whether the validation message should be displayed or not:

private Boolean _itemValidatorDisplayed;
public Boolean ItemValidatorDisplayed
{
    get { return _itemValidatorDisplayed; }
    set
    {
        _itemValidatorDisplayed= value;
        _OnPropertyChanged("ItemValidatorDisplayed");
    }
}

Add a convertor class that converts bool to visibility:

using System;
using System.Windows;

namespace xxx
{
    public class BoolToVisibilityConverter : IValueConverter
    {
        public bool Negate { get; set; }

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool val = System.Convert.ToBoolean(value);
            if (!Negate)
            {
                return val ? Visibility.Visible : Visibility.Collapsed;
            }
            else
            {
                return val ? Visibility.Collapsed : Visibility.Visible;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Bind to the property from the view and apply the converter:

    <UserControl x:Class="ViewClass"
    ...
    >
        <UserControl.Resources>
         <contract:BoolToVisibilityConverter Negate="False"
                  x:Key="BoolToVisibilityConverter" />
        </UserControl.Resources>

...

<TextBlock Visibility="{Binding Converter={StaticResource BoolToVisibilityConverter}, Path=ItemValidatorDisplayed}" />

...

</UserControl>

You'll need to be setting the ViewModel as the datacontext of the view:

namespace xxx
{
    public partial class ViewClass: UserControl
    {
        public ViewClass()
        {
            InitializeComponent();

            this.DataContext = new ViewClass_ViewModel();
        }
}
}

Bingo - perfectly working validation being pushed to any view that cares to subscribe to this ViewModel / Property.

HTH, Mark

Mark Cooper
A: 

You will also be able to bind validation to the source object collection in SL3 :-)

Mark Cooper