6

I created a project using MVVM pattern (or so I thought ;) ). To simplify my case:

Model:

public class Model {
    public string Name { get; set; }
    public bool IsDefective { get; set; }
}

ViewModel - extends MvvmLight ViewModelBase:

public class ViewModel : ViewModelBase {
    private ObservableCollection<Model> models;
    public ObservableCollection<Model> Models {
        get {
            if (_models== null) {
                _models= new ObservableCollection<Models>();
            }

            return _models;
        }
        set {
            RaisePropertyChanged("Models");

            _models= value;
        }
    }
}

View - I'm showing a list of textboxes:

<TextBlock Text="{Binding Name}">
    <TextBlock.Style>
        <Style TargetType="{x:Type TextBlock}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Path=.IsDefective}" Value="True">
                    <Setter Property="Foreground" Value="Red" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </TextBlock.Style>
</TextBlock>

My scenario is like so: some methods in the Model class may change the IsDefective property, but since my model does not implement the INotifyPropertyChanged interface, my view does not know about such changes. How should this problem be resolved "the mvvm way"? I stumbled upon this question here in SO, but to be honest after reading both highest voted answers and the discussion in comments, I'm more confused: MVVM - PropertyChanged in Model or ViewModel? . I'm willing to agree with Jonathan Allen, because it's just more natural for me to bind this way, but as a beginner in the mvvm pattern I may be missing something. So, am I?

Community
  • 1
  • 1
Marek M.
  • 3,799
  • 9
  • 43
  • 93
  • Your not missing any thing , Mvvm and it's counter parts are a suggestion , it helps you create maintainable , testable and decoupled pieces of code. it is perfectly legitimate for your model to implement INotifyPropertyChanged. It's very popular in 'CRUD' applications. – eran otzap Sep 07 '14 at 20:05
  • _"...some methods in the Model class may change the IsDefective property..."_ Could you please clarify **which class** calls the method of Model (the method that may change the `IsDefective` property)? – Sergey Vyacheslavovich Brunov Sep 07 '14 at 22:16

2 Answers2

12

Generally you want your model to be a dumb data transfer object. When you do a database query, you get a dumb model back that doesn't do any transformations because otherwise you're failing to follow Separation of Concerns in SOLID principals. However, cheating a little won't kill you, but it might make debugging something a little frustrating because most people won't expect their POCO (plain old CLR object) to initiate any business logic.

Here's some code:

Some setup classes:

ViewModelBase.cs

A "smarter" version of the ViewModelBase from galasoft, this bad boy autowires up design time view models (you'll like this one)

namespace WPFPlayground.ViewModel
{
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public void SetValue<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
        {
            if (property != null)
            {
                if (property.Equals(value)) return;
            }

            OnPropertyChanged(propertyName);
            property = value;
        }

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

DefectiveToBackgroundColorConverter.cs

A value converter for our use when our product is being displayed on the view (you'll see it referenced later):

using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;

namespace WPFPlayground
{
    public class DefectiveToBackgroundColorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (System.Convert.ToBoolean(value))
            {
                return new SolidColorBrush(Colors.Red);
            }
            return new SolidColorBrush(Colors.White);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Binding.DoNothing;
        }
    }
}

Using Model-first method:

ProductModel.cs

POCO DTO

namespace WPFPlayground.Model
{
    public class ProductModel
    {
        public string Name { get; set; }
        public bool IsDefective { get; set; }
    }
}

ProductViewModel.cs

Notice the use of setvalue to automatically wire up the notifypropertychanged event.

namespace WPFPlayground.ViewModel
{
    public class ProductViewModel : ViewModelBase
    {
        private string _name;
        private bool _isDefective;

        public bool IsDefective
        {
            get { return _isDefective; }
            set { SetValue(ref _isDefective, value); }
        }

        public string Name
        {
            get { return _name; }
            set { SetValue(ref _name, value); }
        }
    }
}

So we have a productmodel and a productviewmodel. One does all the work when you're interacting with the database, and one does all the work when you bind to your views.

So we'll need a view that represents just a single productviewmodel:

ProductView.xaml

Notice the use of the background color converter to handle our triggers

<UserControl x:Class="WPFPlayground.View.ProductView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:wpfPlayground="clr-namespace:WPFPlayground"
             mc:Ignorable="d" 
             d:DataContext="{d:DesignInstance wpfPlayground:DesignProductViewModel, IsDesignTimeCreatable=True}">
    <UserControl.Resources>
        <wpfPlayground:DefectiveToBackgroundColorConverter x:Key="DefectiveToBackgroundColorConverter" />
    </UserControl.Resources>
    <Viewbox>
        <Border Width="500" Background="{Binding IsDefective, Converter={StaticResource DefectiveToBackgroundColorConverter}}">
            <TextBlock Text="{Binding Name}" FontSize="40" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center" />
        </Border>
    </Viewbox>
</UserControl>

Next we'll need that design time viewmodel so we can view our XAML in design time:

DesignProductViewModel.cs

A bit boring, but it makes design time work!

using WPFPlayground.ViewModel;

namespace WPFPlayground
{
    public class DesignProductViewModel : ProductViewModel
    {
        public DesignProductViewModel()
        {
            Name = "This is my product";
            IsDefective = true;
        }
    }
}

Now we need to display a list of these viewmodels:

MainWindow.xaml

Itemscontrol all day err day

<Window x:Class="WPFPlayground.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:viewModel="clr-namespace:WPFPlayground.ViewModel"
        xmlns:view="clr-namespace:WPFPlayground.View"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" d:DataContext="{d:DesignInstance viewModel:DesignProductsViewModel, IsDesignTimeCreatable=True}">
    <Window.Resources>
        <DataTemplate DataType="{x:Type viewModel:ProductViewModel}">
            <view:ProductView />
        </DataTemplate>
    </Window.Resources>
    <StackPanel>
        <ItemsControl ItemsSource="{Binding Products}">
            <view:ProductView />
        </ItemsControl>
    </StackPanel>
</Window>

DesignProductsViewModel.cs

The design time view model so you can see this working in design time. It generates an easy random set of products.

using System;
using System.Collections.ObjectModel;
using System.Linq;

namespace WPFPlayground.ViewModel
{
    public class DesignProductsViewModel : ProductsViewModel
    {
        public DesignProductsViewModel()
        {
            var random = new Random();
            Products = new ObservableCollection<ProductViewModel>(Enumerable.Range(1, 5).Select(i => new ProductViewModel
            {
                Name = String.Format(@"Product {0}", i),
                IsDefective = (random.Next(1, 100) < 50)
            }));
        }
    }
}
C Bauer
  • 5,003
  • 4
  • 33
  • 62
  • I agree that your DTO's can stay 'DUMB' you can create a wrapper for the UI side for your Dto's where it implement's INotifyPropertyChanged but most of the time it's just redundant and inconvenient .. – eran otzap Sep 07 '14 at 20:14
  • 1
    Words like redundant and inconvenient are things people use to dismiss TDD, SOLID principals, and other beneficiary practices. Just because it's inconveniant, doesn't mean it should be discarded. – C Bauer Sep 07 '14 at 20:15
  • So in your opinion - in this particular scenario - should I create an additional layer for my view to model binding, or should I cheat? I've written some test cases and it was easy - I had no trouble with this code, but maybe the additional layer would make writing tests even more easy (once again - in this particular case)? – Marek M. Sep 07 '14 at 20:18
  • In other words - what benefits would I get in this case if I'd not be exposing model directly to the view? It's a simple example, but my application will grow. – Marek M. Sep 07 '14 at 20:20
  • 2
    It's completely up to you my friend. In my applications, there would have been a "Model"ViewModel and I would have done my notifypropertychanged attributes there. That is the purpose of MVVM. If you add this little smidge of business logic to your model, what happens if a new business requirement turns up? You add it there too, I guess. Eventually the model, expecting to be a DTO, is doing a bunch of business logic when it's just supposed to be a DTO. In small projects it probably won't burn you. – C Bauer Sep 07 '14 at 20:23
  • So in this case - could you possibly give me an example code I could work on? – Marek M. Sep 07 '14 at 20:32
  • OKAY! I put something up for you friend. If you want I'll post it to github where it might be easier to look at. – C Bauer Sep 07 '14 at 21:45
1

Your not missing any thing , Mvvm and it's counter parts are suggestions which help you create maintainable , testable and decoupled pieces of code.

When you come across a situation where you duplicate code just to satisfy Mvvm you can "cheat".

It is perfectly legitimate for your model to implement INotifyPropertyChanged. It's very popular in 'CRUD' applications.

eran otzap
  • 12,293
  • 20
  • 84
  • 139