Click here to Skip to main content
15,884,793 members
Articles / Desktop Programming / WPF

MVVM in WPF Part II

Rate me:
Please Sign up or sign in to vote.
4.55/5 (7 votes)
22 Apr 2009CPOL7 min read 42.3K   21   9
This is the second part in a two part article on the Model-View-ViewModel design pattern in WPF.The ModelOur friend the Model is responsible for getting and holding onto the data. The data could come from anywhere, a file, a database, thin air, etc.

This is the second part in a two part article on the Model-View-ViewModel design pattern in WPF.

The Model

Our friend the Model is responsible for getting and holding onto the data. The data could come from anywhere, a file, a database, thin air, etc. Our Model is the one that is going to read the data out of the file and store it in a DataTable so that the ViewModel can present it to the View. So add a Model class to the Model project. I called mine “MainModel” because it’s going to the MainWindow. We are going to need three things in our Model, a constructor, a method to get the data from the file, and a property to expose the data to the outside world. The method is easy as we are just going to copy the same code from the previous article and make minor modifications to it. So here is our GetFileData method.

C#
public bool GetFileData(string filename)
{
    const string connectionString =
    @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\csvfiles\;Extended Properties=’text;HDR=Yes’";
    string commandText = string.Format("Select * from {0}", filename);
    OleDbConnection conn = new OleDbConnection(connectionString);

    conn.Open();

    OleDbDataAdapter da = new OleDbDataAdapter(commandText, conn);
    DataSet temp = new DataSet();
    da.Fill(temp);
    FileData = temp.Tables[0];
    return true;
}

Here we are opening a file from the C:\csvfiles directory and filling a DataTable with the results. FileData is the name of the property that is holding the data. So lets add that next.

C#
public DataTable FileData
{
    get; set;
}

Pretty simple. The only thing left to do is to add the constructor.

C#
public MainModel()
{
    FileData = new DataTable();
}

That’s it for the Model. Remember to reference the System.Data and OleDb namespaces for the GetFileData method. Here is the complete listing for the Model.

C#
namespace Article.Model
{
    using System.Data;
    using System.Data.OleDb;
 
    public class MainModel
    {
        public MainModel()
        {
            FileData = new DataTable();
        }
 
        public DataTable FileData
        {
            get; set;
        }
 
        public bool GetFileData(string filename)
        {
            const string connectionString = 
               @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\csvfiles\;Extended Properties=’text;HDR=Yes’";
            string commandText = string.Format("Select * from {0}", filename);
            OleDbConnection conn = new OleDbConnection(connectionString);
 
            conn.Open();
 
            OleDbDataAdapter da = new OleDbDataAdapter(commandText, conn);
            DataSet temp = new DataSet();
            da.Fill(temp);
            FileData = temp.Tables[0];
            return true;
        }
    }
}

Nothing to it. That is it for the Model. Next we will move to the ViewModel.

The ViewModel

Most of the action happens in the ViewModel so it naturally follows that the ViewModel is the most complex which was my stumbling block. There is so much that can, and does, go on in there that it can quickly get confusing. The very first thing to do is to create a base ViewModel class that we could use to base our ViewModels on. For this simple example it really is not necessary but it does serve as a good base to build from. So add a new class to the ViewModels folder in the project and call it BaseViewModel. Make the base class abstract and implement the INotifyPropertyChanged and IDisposable interfaces. Implementing those interfaces in the base class saves us from having to implement them in each and every other ViewModel. Some of this code is strictly for debugging purposes and those are commented. I believe that 99% of the base class code was Josh Smiths. I added some code comments but that was about it. This is the code stripped down. Not much to it and should be pretty much self explanatory.

C#
public abstract class BaseViewModel : INotifyPropertyChanged, IDisposable
{
    protected BaseViewModel()
    {
    }

    ~BaseViewModel()
    {
        string msg = string.Format("{0} ({1}) ({2}) Finalized",
            GetType().Name, DisplayName, GetHashCode());
        Debug.WriteLine(msg);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public virtual string DisplayName
    {
        get; protected set;
    }

    protected virtual bool ThrowOnInvalidPropertyName
    {
        get; private set;
    }

    public void Dispose()
    {
        OnDispose();
    }

    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    public void VerifyPropertyName(string propertyName)
    {
        // Verify that the property name matches a real,
        // public, instance property on this object.
        if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        {
            string msg = "Invalid property name: " + propertyName;

            if (ThrowOnInvalidPropertyName)
            {
                throw new Exception(msg);
            }

            Debug.Fail(msg);
        }
    }

    protected virtual void OnDispose()
    {
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        VerifyPropertyName(propertyName);

        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler == null)
        {
            return;
        }

        var e = new PropertyChangedEventArgs(propertyName);
        handler(this, e);
    }
}

Now we are ready for the MainViewModel. Add a new class to the ViewModels and name it MainViewModel. Make sure to derive from the BaseViewModel class. Because there is a lot of stuff going on in here, we are going to take it nice and easy getting through this. This is the class definition.

C#
public class MainViewModel : BaseViewModel
{
}

The two things that we need right now is two private variables, one for the Model and one for the data that the View is going to display.

C#
private readonly MainModel _model;

private DataTable _fileData;

Because I am simple minded, we are going to initialize the variables in the constructor:

C#
public MainViewModel()
{
    _model = new MainModel();
    _fileData = new DataTable();
}

Now we need a method to call to get the data from the model:

C#
public void GetData()
{
    _model.GetFileData("test.csv");
    FileData = _model.FileData;
}

We have hard code a file name here but, if you wanted to you could open a file browser and let the user select a file to open.

And then a property to expose the data to view:

C#
public DataTable FileData
{
    get
    {
        return _fileData;
    }

    set
    {
        _fileData = value;
        OnPropertyChanged("FileData");
    }
}

Notice that the property called OnPropertyChanged in the setter. When that happens the view is automatically updated with the changed information. Magic!

As long as we are here we are going to introduce a Command. The command that we are going to use is going to get used when the user clicks the Button on the main window. The command will get routed to the ViewModel and the ViewModel will handle the command. We are going to need another variable for the command:

C#
private RelayCommand _getDataCommand;

We are going to use the RelayCommand class to encapsulate the RoutedCommand. The RelayCommand class was also written by Josh Smith. Now to expose the command to the View we do this:

C#
public ICommand GetDataCommand
{
    get
    {
        if (_getDataCommand == null)
        {
            _getDataCommand = new RelayCommand(param => GetData());
        }

        return _getDataCommand;
    }
}

The main point of this is the initialization of the Command. The RelayCommand is passed the method that it is going to call when the button is clicked. In this case, the GetData method is going to be that method.

As hard as it is to believe, that is it for the hardest part. After the code listing we are off to the View. This is the complete listing for the MainViewModel:

C#
public class MainViewModel : BaseViewModel
{
    private readonly MainModel _model;

    private DataTable _fileData;

    private RelayCommand _getDataCommand;

    public MainViewModel()
    {
        _model = new MainModel();
        _fileData = new DataTable();
    }

    public DataTable FileData
    {
        get
        {
            return _fileData;
        }

        set
        {
            _fileData = value;
            OnPropertyChanged("FileData");
        }
    }

    public ICommand GetDataCommand
    {
        get
        {
            if (_getDataCommand == null)
            {
                _getDataCommand = new RelayCommand(param => GetData());
            }

            return _getDataCommand;
        }
    }

    public void GetData()
    {
        _model.GetFileData("test.csv");
        FileData = _model.FileData;
    }
}

The View

The view is oh so simple, really. The first thing that you need to do is to go over to www.codeplex.com and get the WPFToolkit, http://www.codeplex.com/wpf. I used the data grid control for WPF for this example. So this will also show you how to use the data grid in a most basic fashion. So after you install the toolkit you will need to add a reference to the WPFToolkit.dll to the View project. Once you do that, you can add the namespace reference to the View XAML file. Open up the MainWindow.xaml file and add the namespace to the top of the file:

XML
xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WpfToolkit"

This tells the program that we are going to be using the Microsoft.Windows.Controls namespace and we are going to reference in our code as “dg”. Once we have the namespace mapped in, we can include the data grid control and binding it to our data source:

<dg:DataGrid ItemsSource="{Binding Path=FileData}" Margin="0,30,0,0″ />

The ItemsSource property is binding to the FileData property of the ViewModel. And that is all you need to do here to get the data grid to show the data. Cool huh? The only other thing that we are going to do here is to add a button that we can use to tell the ViewModel to go get the data:

<Button Height="22″HorizontalAlignment="Left"
    Margin="8,4,0,0″ Name="button1″VerticalAlignment="Top" Width="48″
    Command="{Binding Path=GetDataCommand}">Button</Button>

The important part of this is the “Command=”{Binding Path=GetDataCommand}”” part. This is the part that tells the program that when the button is clicked that it is to execute the GetDataCommand. If you recall the GetDataCommand is the command in the ViewModel that gets the data from the Model. There is not an event handler in the traditional sense. More of the magic of WPF. That is it for the XAML for the MainWindow. The XAML for the MainWindow now looks like this:

XML
<Window x:Class="Article.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WpfToolkit"
    Title="MainWindow" Height="300″ Width="300″>
    <Grid>
        <dg:DataGrid ItemsSource="{Binding Path=FileData}" Margin="0,30,0,0″ />
        <Button Height="22″HorizontalAlignment="Left" Margin="8,4,0,0″
            Name="button1″VerticalAlignment="Top" Width="48″
            Command="{Binding Path=GetDataCommand}">Button</Button>
    </Grid>
</Window>

Now, before we continue I have to say that this in not the only way to code this up. You caninstantiate the ViewModel in the XMAL and not do anything at all in the code behind however, I am going to save that for another time. That being said open up the MainWindow.cs file and find the constructor for the window. Right after the InitializeComponent method call, add the following:

DataContext = new MainViewModel();

That is all you need to do to set the DataContext of the window which is where the data binding looks to find the data. You will also need to add a reference to the ViewModel assembly. This is the MainWindow code:

C#
namespace Article
{
    using System.Windows;
    using ViewModel.ViewModels;
 
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainViewModel();
        }
    }
}

Conclusion

That completes the discussion of the basic architecture of a WPF MVVM application. We created a basic Model class that read a .csv file and exposed that data as a property of the class. We created a base ViewModel class to serve as a base from which to build upon and created a derived ViewModel to bind the data to the View raising a NotifyPropertyChanged event to update the data binding. We also included in our ViewModel a command using a RelayCommand that would be used from the View to go get the data and the View displayed the data in the WPF data grid control through data binding. Attached is a Visual Studio 2008 solution containing all of the code discussed in this article, including the .csv file used to display data in the data grid. Rename the .doc file to .zip before trying to open it. This is a wordpress.com requirement. articlezip

This article was originally posted at http://wesaday.wordpress.com?p=144

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
Questionworks fine with csv files; has problems with mdb Pin
taleofsixstrings3-Feb-14 22:13
taleofsixstrings3-Feb-14 22:13 
GeneralUsing Merge with the Datatable Pin
Gideons30016-Oct-09 7:34
Gideons30016-Oct-09 7:34 
GeneralRe: Using Merge with the Datatable Pin
Wes Aday16-Oct-09 11:44
professionalWes Aday16-Oct-09 11:44 
GeneralExcellent Pin
Richard MacCutchan28-Apr-09 3:38
mveRichard MacCutchan28-Apr-09 3:38 
GeneralRe: Excellent Pin
Wes Aday28-Apr-09 7:51
professionalWes Aday28-Apr-09 7:51 
General[Message Deleted] Pin
stecevsrf18-Aug-09 16:26
stecevsrf18-Aug-09 16:26 
GeneralRe: ONLY GOOD IF METHOD HAS NO PARAMETERS Pin
Gideons30016-Oct-09 7:21
Gideons30016-Oct-09 7:21 
GeneralShort & straight Pin
Nedim Sabic23-Apr-09 22:55
Nedim Sabic23-Apr-09 22:55 
I really enjoyed reading this. Great job Smile | :)
GeneralRe: Short & straight Pin
Wes Aday24-Apr-09 3:13
professionalWes Aday24-Apr-09 3:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.