Click here to Skip to main content
11,647,810 members (60,864 online)
Click here to Skip to main content

WPF Charting using MVVM Pattern

, 19 Oct 2010 CPOL 259.2K 3.8K 61
Rate this:
Please Sign up or sign in to vote.
Shows how to implement a WPF Chart in .NET 4.0 using MVVM Pattern

Introduction

There are tons of articles on the MVVM (Model-View-ViewModel) design pattern, and rightfully so. It's an easy pattern to understand (after you've studied it for a while) and it very nicely separates concerns. Where better to separate concerns than with a Chart and its data? This article shows you how to use the .NET Framework's (free) Datavisualization Chart component using the MVVM model.

Background

This sample was built using Visual Studio 2010 running .NET Framework 4.0. It also uses the WPF Toolkit version 3.5 which has the System.Windows.Controls.DataVisualizationnamespace. You can download this toolkit from Codeplex here. This solution is new to CodeProject as the previous examples of the DataVisualizationnamespace were either all for ASPX Charting or WinForms Charting. This particular project is for WPF and includes the entire current set of ChartTypesfound in the DatavisualizationNamespace.

Using the Code

The zip file for this article (minus the DataVisualizationcomponent) is an entire project that allows you to compile and run a sample of every chart type currently supported in WPF. Simply run the project in debug mode, once it comes up, just change the SeriesTypesas shown below on the left, to change the view. Notice below that the Title, Legend Title are also editable. This allows one to see how it was done and add other editable attributes to the Charting component.

ColumnSeries.png

Points of Interest

The View

Views in the MVVM pattern are just the markup in XAML needed for displaying content and style. Be careful when putting code function in the "code-behind" of the View. One typically wants to limit that code to View Only logic such as Animation. The content of the View is handled in the ViewModel via WPF Binding. The MainWindow("Shell") XAML is shown below:

<Window
    x:Class="WPFCharting.Views.MainWindowView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"          
    xmlns:cmd="clr-namespace:WPFCharting.Commands" 
    xmlns:ViewModels="clr-namespace:WPFCharting.ViewModels" 
    xmlns:Views="clr-namespace:WPFCharting.Views" 
    Title="MainWindowView" 
    MinHeight="400"
    MinWidth="800"
    Height="Auto" Width="Auto">
    <Window.Resources>
        <ViewModels:MainWindowVM x:Key="VM"></ViewModels:MainWindowVM>
        <cmd:ShowAlterData x:Key="SAD"></cmd:ShowAlterData>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="21*"></ColumnDefinition>
            <ColumnDefinition Width="80*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Border Padding="2" >
            <Border.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,.7">
                    <GradientStop Color="White" Offset="0" />
                    <GradientStop Color="Black" Offset="1" />
                </LinearGradientBrush>
            </Border.Background>
            <Border Padding="1" >
                <Border.Background>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="Black" Offset="0" />
                        <GradientStop Color="White" Offset="1" />
                    </LinearGradientBrush>
                </Border.Background>
                <StackPanel Grid.Column="0" Margin="5,5,5,0" 
                    DataContext="{Binding Source={StaticResource VM}}">
                    <StackPanel.Background>
                        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                            <GradientStop Color="Black" Offset="0" />
                            <GradientStop Color="White" Offset="1" />
                        </LinearGradientBrush>
                    </StackPanel.Background>
                    <Views:UcTitle x:Name="XTitle" ></Views:UcTitle>
                    <Views:UcLegendTitle x:Name="XLegendTitle"/>
                    <Views:UcSeriesTitle x:Name="XSeriesTitle" />
                    <Views:UcSeriesTypes x:Name="XSeriesTypes"/>
                    <Border Padding="2,5,2,5" Background="Black">
                        <StackPanel>
                            <Label Foreground="LightBlue" 
				HorizontalContentAlignment="Center">Edit</Label>
                            <Button Command="{Binding Source={StaticResource SAD}}">
				Show/Alter Data</Button>
                            <Button>Reset To Original</Button>
                        </StackPanel>
                    </Border>
                </StackPanel>
            </Border>
        </Border>
        <ContentControl  Grid.Column="1" 
            DataContext="{Binding Source={StaticResource VM}}" 
            Content="{Binding Source={StaticResource VM}, Path=MainChart}" />
    </Grid>
    <Window.Background>
        <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
            <GradientStop Color="Black" Offset="0.016" />
            <GradientStop Color="White" Offset="0" />
            <GradientStop Color="Black" Offset="0.205" />
            <GradientStop Color="Black" Offset="0.079" />
            <GradientStop Color="White" Offset="0.212" />
        </LinearGradientBrush>
    </Window.Background>
</Window>

Notice there's really not much there, as it is really just a "Shell". There are just two "ContentControls" in the Shell, one is the StackPanelcontaining the Left side Editing UserControls and the other is the Content Control which "houses" the charts. One note of interest here is that the DataContextof the ChartControlhad to be set explicitly as shown (as well as the Content and the Path) statements. Setting the DataContextanywhere else in this project failed. For example, it didn't work in the Grid. Perhaps I misspelled something, but it worked in the ContentControlso I left it there.

The ViewModelwhich contains (among other things) property getters and setters has a number of private/publicproperties which are for the WPF MainWindow(Shell). The call to PropChangedis how the ViewModelnotifies the View. Those property getters/setters take on a look like this:

private ContentControl _mainChart;
        /// <summary>
        /// When swapping ChartTypes this is the content which is injected to view
        /// </summary>
        public ContentControl MainChart {
            get { return _mainChart; }
            set {
                PreviousViewList.Add(_mainChart);
                _mainChart = value;
                PropChanged("MainChart");            
            } 
        }

So in the code above, we see the "pattern" of MVVM property "getters/setters". The privatevariable encapsulates the public getand setmethods, the setmethod will then update the privatevariable and call PropChanged.

One question that arises is, how do we boot up a MVVM application. In this solution, you will find it being done in the App.cs file using this code:

public partial class App : Application
{
  protected override void OnStartup(StartupEventArgs e)
  {
    MainWindow mw = new MainWindow();
    mw.Show();
 }

The OnStartupevent is overridden and the MainWindowis instantiated and shown. From there, the Main Window will instantiate the MainWindowViewModelas shown next. The MainWindowis shown, but how do we start the MainWindowViewModel?

In the MainWindow, we see the MainWindowViewModel's class instantiation happening via XAML. This is done in the Window.Resoucessection of the MainWindow.Xaml file. The MVVM pattern states "The View knows the View-Model, and the View-Model knows the Model".

<Window.Resources>
<local:MainWindowVM x:Key="VM"></local:MainWindowVM>
</Window.Resources>

Note however that Binding between the Viewand the Viewmodelis done via the WPF Binding system so that when a project is run, the getter methods are called at View.Show()time. It (the ViewModel) then becomes a staticobject, which the charting Series UserControls (of this project) use to bind data. We gave it a name of "VM" for View Model as shown above.

Static Resources and WPF Binding is not a topic for this article; however, it pays to know how they work because certain aspects of the MVVM pattern use XAML based resources as shown above. You can read these articles on MVVM Resources.

Ahh! And now for the Markup that controls which type of ChartSeriesis shown. Each of the UserControlsin this project represent a different ChartView(Such as this Area control User Control). For Charting, a chart markup in XAML can contain a type of Chart Series. In this case (see Markup below), the PieSerieschart type is shown. NOTE: What is lacking in the Charting Framework; however, is an Generic Series object of which to bind to at design time. For example, there is no ChartingToolKit:Series generic type which would allow chart series types to be injected at run time changing the views of data. So as an alternative to this limitation, this project has 1 UserControlper chart type. This was the only way known to the author to do this in XAML.

<chartingToolkit:LineSeriesx:Name="XPieSeries"
DataContext="{Binding}"
DependentValueBinding="{Binding Path=Value}"
IndependentValueBinding="{Binding Path=Key}"
ItemsSource="{Binding Path=data}"/>

If you are experimenting with Chart Series types (see XAML above), make sure to set up the Dependent and Independent Value Bindings to the correct Paths. This author lost a considerable amount of time with charts not showing up simply because the Value, and Key paths were bound to the wrong Axis. Notice also the generic Binding being used, there is no Source specified in these UserControls. This is due to WPFs nice DataContextinheritance effect. The MainWindowViewsets the DataContext, so no explicit DataContextstatements are needed in these UserControlswhich inherit this DataContext.

In the most current iteration of this project, there are 7 different UserControlsfound in the Views folders which are used to swap the content of the MainWindowShell. These UserControlsare the charttype implementations e.g. BarSeries, PieSeries, ColumnSeries, etc. Driven by the command pattern of the MVVM architecture, each of these classes use a static getInstance() getter, setter method to ensure the fastest possible swapping of the Chart Views.

You will find the implementation of the Command to swap the views in the Command folder. It is just a wrapper class invokable from the MainWindowView. This class "ShowSeries" is simply a selectstatement that gets the current instance of the Charttype. It then injects the Chart view into the MainWindow. While this is not a formalized IOC or Dependency Injection pattern, it simulates the same thing. In particular, adding the concept of Singleton Pattern (getInstance) for the sake of speed. BTW you'll find these Views are able to be injected rather fast due to the Singleton getInstancemethod.

The View Model

So what's easier than using an ObservableCollectionof something when working with WPF? It's a wrapper class made just for exposing data to the View. What's really nice is that it is a generic collection that allows you to define the type (to be displayed). These three lines of code is all you need to wire-up a Chart in the View Model. The code in the zip file will show you how to notify the WPF layer of any changes.

private ObservableCollection<KeyValuePair<string, int>> _data;
   public ObservableCollection<KeyValuePair<string,int>> data {
          get { return _data; }          
 }

The XAML above shows how the ViewModeldelivers the data to the chart.

On the left hand side of the MainWindow, there is a "controller" that allows you to edit content within the Chart itself. It uses the ViewModelas does the UserControls(ChartTypes) to getand setthe properties. This is an instance whereby multiple controls are all bound to the same ViewModeland demonstrates how the ViewModelcan host separate concerns. Imagine a Webbrowserapplication that has navigation links on the left. The ViewModelcan actually serve multiple ContentControlsat the same time, without violating Separation of Concerns. However, keep in mind that the adage of "One Time and One Place" an "Each Object is responsible for itself" will guide you in determining how much is too much code within one class. This is why the Commandinterface is nice because it moves implementation out of the ViewModel. In this example, the Commandsback-end the setter properties of the View Model.

The Model

Finally, we come to the point where we implement the Data Layer in the Model for the class. Nothing more than a concrete class of Observablecollection of a KeyValuePair<string,int> type:

public class MainWindowModel : ObservableCollection<KeyValuePair<string, int>>
 {
    public MainWindowModel()
      {
          init();
       }
    public void init()
    {
        Add(new KeyValuePair<string, int>("Dog", 30));
        Add(new KeyValuePair<string, int>("Cat", 25));
        Add(new KeyValuePair<string, int>("Rat", 5));
        Add(new KeyValuePair<string, int>("Hampster", 8));
        Add(new KeyValuePair<string, int>("Rabbit", 12));
     }
     public ObservableCollection<KeyValuePair<string, int>> getData()
     {
        return this;
        }
    }

Voila! That's all there is to this. Now imagine that you want to have SQL Connections or XML Readers providing content. As you can see, you have many options. You can create new methods within this class above, or you can create other back-end classes that update this class. The good news is that if you are able to follow this contract of a KeyValuePair<string,int> then the implementation is hidden from the View. You could have many different getDatamethods in this class.

History

  • 4th Update: Fixed Links in main article 10/19/2010
  • 3rd Update: Clarified the View's Code Behind Statement, added links to Browse Code. Clarified other content. 10/15/2010
  • 2nd Edition: Included all Chart Types, MVVM Commanding, View Injection as well as good examples of Binding, to show how to change content in a chart after it shows data. Lots of comments in the code. 10/12/2010 XZZ0195
  • Kenny Rogers and the 1st Edition: 10/12/2010 XZZ0195

License

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

Share

About the Author

Mr. Javaman
Software Developer (Senior)
United States United States
Mr Javaman

You may also be interested in...

Comments and Discussions

 
QuestionA bug Pin
Tae-Sung Shin31-Jul-13 8:07
memberTae-Sung Shin31-Jul-13 8:07 
Questionhow to use two chart in the same view or the same user control in many views. Pin
Daimroc19-Feb-12 1:29
memberDaimroc19-Feb-12 1:29 
AnswerRe: how to use two chart in the same view or the same user control in many views. Pin
xzz019524-Mar-12 11:23
memberxzz019524-Mar-12 11:23 
QuestionControl the color for chart Pin
Member 787310329-Sep-11 20:12
memberMember 787310329-Sep-11 20:12 
QuestionError when reselecting a series. Pin
Member 798362728-Jul-11 4:27
memberMember 798362728-Jul-11 4:27 
AnswerRe: Error when reselecting a series. Pin
xzz019529-Jul-11 17:26
memberxzz019529-Jul-11 17:26 
GeneralFiltering Data in using Datatables to build ObservableCollection<keyvaluepair<string,int> Pin
xzz01952-Nov-10 13:17
memberxzz01952-Nov-10 13:17 
GeneralRe: Filtering Data in using Datatables to build ObservableCollection Pin
Member 798362728-Jul-11 5:43
memberMember 798362728-Jul-11 5:43 
GeneralRe: Filtering Data in using Datatables to build ObservableCollection Pin
xzz019529-Jul-11 17:26
memberxzz019529-Jul-11 17:26 
Wasn't too clear was it? Ok what I did was create a filter list of radio buttons to show the end user. When they clicked a radio button this was the handler. The radiobutton content was set to the columnname. the _DelegateDT is the data table data.

Hope that helps!
GeneralWPF Chart animation Pin
Boško Stupar31-Oct-10 22:13
memberBoško Stupar31-Oct-10 22:13 
GeneralRe: WPF Chart animation Pin
xzz019529-Jul-11 17:34
memberxzz019529-Jul-11 17:34 
GeneralRe: WPF Chart animation Pin
xzz01953-Aug-11 4:28
memberxzz01953-Aug-11 4:28 
GeneralMy vote of 5 Pin
Sparkling_ouc18-Oct-10 21:42
groupSparkling_ouc18-Oct-10 21:42 
GeneralMy vote of 3 Pin
Maxim Maximov15-Oct-10 12:57
memberMaxim Maximov15-Oct-10 12:57 
GeneralMessage Removed Pin
xzz019515-Oct-10 13:13
memberxzz019515-Oct-10 13:13 
GeneralRe: My vote of 3 Pin
Maxim Maximov15-Oct-10 22:16
memberMaxim Maximov15-Oct-10 22:16 
GeneralRe: My vote of 3 Pin
xzz019516-Oct-10 0:15
memberxzz019516-Oct-10 0:15 
GeneralRe: My vote of 3 Pin
Pete O'Hanlon15-Oct-10 21:20
mvpPete O'Hanlon15-Oct-10 21:20 
GeneralCode has been scanned by Resharper... Pin
xzz019516-Oct-10 1:16
memberxzz019516-Oct-10 1:16 
GeneralA "dangerous" statement. Pin
Pete O'Hanlon14-Oct-10 23:57
mvpPete O'Hanlon14-Oct-10 23:57 
GeneralRe: A "dangerous" statement. [modified] Pin
xzz019515-Oct-10 4:11
memberxzz019515-Oct-10 4:11 
GeneralRe: A "dangerous" statement. Pin
Pete O'Hanlon15-Oct-10 4:47
mvpPete O'Hanlon15-Oct-10 4:47 
GeneralRe: A "dangerous" statement. Pin
xzz019515-Oct-10 5:48
memberxzz019515-Oct-10 5:48 
GeneralRe: A "dangerous" statement. Pin
Pete O'Hanlon15-Oct-10 10:27
mvpPete O'Hanlon15-Oct-10 10:27 
GeneralRe: A "dangerous" statement. Pin
xzz019515-Oct-10 11:02
memberxzz019515-Oct-10 11:02 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150804.4 | Last Updated 19 Oct 2010
Article Copyright 2010 by Mr. Javaman
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid