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)
}));
}
}
}