Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to add dynamic images/buttons to a row in a DataGrid?

0.00/5 (No votes)
10 Apr 2010 1  
Often you want to display a DataGrid, but you don’t want to simply display it as is, you want to be able to enhance it and add functionality to it, such as adding an image to the start of each row or adding a button on each row.
Title:       How to add a dynamic image and/or a dynamic button to a DataGrid row using a DataGridTemplateColumn and a DataTemplateSelector?
Author:      Jared Barneck 
Email:       rhyous at yahoo
Blog:        http://rhyous.com
Member ID:   6636137
Language:    C#, WPF
Platform:    Windows
Technology:  WPF, WPFToolKit, Visual Studio 2008
Level:       Beginner
Description: Enter a brief description of your article
Section      Platforms, Frameworks & Libraries 
SubSection   Windows Presentation Foundation » Controls
License:     The BSD License 

Introduction 

This article was originally published on my blog here. I wanted to share this with CodeProject to give back as I have learned a lot from this site.

Often you want to display a DataGrid, but you don't want to simply display it as is, you want to be able to enhance it and add functionality to it, such as adding an image to the start of each row or adding a button on each row.

So in a WPFToolkit DataGrid you can add columns and have data or a control, such as an image or button, added to each row. Even better is that you can read the row and dynamically decide which data or control to add in each row.

For my use case, we are doing something similar to PC Doctor, where we check a bunch of values, compare them to defaults and then display a grid that show whether the state is Normal, Warning, or Error. We want to display a different image for each state. Also if the state is warning or error, we want a "Fix" button.

To start, we have a WPF project in Visual Studio 2008. We have installed the WPFToolKit and have added a reference to it in our project.

We create a table using a DataTable that looks as follows:

IntVal StrVal
0 normal
1 warning
2 error

In code, you can assign a DataTable to the DataGrid's DataContext and it will do the work for you to display the DataGrid.

As we pass this to a DataGrid we want to add two columns:

  1. We want to add an image that is different if it is normal, warning, or error.
  2. WE want to add a button only if it is warning or error.

So the visual would look as follows:

Image IntVal StrVal Action
0 normal  
1 warning <button>Fix</button>
2 error <button>Fix</button>

Step 1. Install prerequisites: Install Visual Studio 2008, and download and install the WPFToolkit.

You probably already have this done, and there are no steps for provided for these.

Step 2. Create a new WPF project in Visual studio 2008 and design the WPF interface

So once our project was created and the reference to WPFToolKit added, we then changed the XAML on our default Window1 class.

  1. We need to add a reference to the toolkit here as an xmlns in the window tag (See line 4).
  2. We need to add resources for our button. This is done in the Windows.Resources section (between lines 6 and 21). Each resource is a DataTemplate.
  3. We need to add three separate resources for our images. Again, each resource is a DataTemplate.
  4. We need to set up the Source value for each Image. For now, we are going to put in static paths to image files. We also specify that no matter what the image size, we want it to display as 16x16. See the bottom of this article for more information on using images.
  5. We need to add a DataGrid from the WPFToolkit reference. (See line 23)
  6. We need to copnfigure the DataGrid's ItemSource to use Binding, otherwise, it won't display the DataTable when it is assigned to the DataContext. (See line 23)
Window1.xaml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
<Window x:Class="DataGridAddButtonAndImageColumns.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:wpftk="http://schemas.microsoft.com/wpf/2008/toolkit"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <DataTemplate x:Key="FixThisTemplate">
            <Button Name="mButtonFixThis" Click="ButtonFixThis_Click">Fix This</Button>
        </DataTemplate>
        <DataTemplate x:Key="NormalTemplate">
        </DataTemplate>
        <DataTemplate x:Key="StatusTemplateNormal" x:Name="mNormalImage">
            <Image Width="16" Height="16" Source="C:\DataGridAddButtonAndImageColumns\DataGridAddButtonAndImageColumns\bin\Debug\Normal.png" />
        </DataTemplate>
        <DataTemplate x:Key="StatusTemplateWarning" x:Name="mWarningImage">
            <Image Width="16" Height="16" Source="C:\DataGridAddButtonAndImageColumns\DataGridAddButtonAndImageColumns\bin\Debug\Warning.png" />
        </DataTemplate>
        <DataTemplate x:Key="StatusTemplateError" x:Name="mErrorImage">
            <Image Width="16" Height="16" Source="C:\DataGridAddButtonAndImageColumns\DataGridAddButtonAndImageColumns\bin\Debug\Error.png" />
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <wpftk:DataGrid Name="mDataGrid" ItemsSource="{Binding}" CanUserAddRows="False" IsReadOnly="True"></wpftk:DataGrid>
    </Grid>
</Window>

Step 3 - Create the data 

The data can come from anywhere but for this basic example, we are just statically creating a DataTable in the Constructor. This is not a document on creating a DataTable, so we are not going to describe how it is done, but here is the code.

I will note that we add a property for the DataTable and the DataTable.DefaultView so that they can be easily accessed.

Data.cs
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
using System.Data;

namespace DataGridAddButtonAndImageColumns
{
    public class Data
    {
        #region Member Variables
        private DataTable mTable;
        #endregion

        #region Constructors

        /*
         * The default constructor
         */
        public Data()
        {
            mTable = new DataTable();
            mTable.Columns.Add("IntVal", typeof(int));
            mTable.Columns.Add("StrVal", typeof(string));
            DataRow row0 = mTable.NewRow();
            row0["IntVal"] = 0;
            row0["StrVal"] = "normal";
            mTable.Rows.Add(row0);

            DataRow row1 = mTable.NewRow();
            row1["IntVal"] = 1;
            row1["StrVal"] = "warning";
            mTable.Rows.Add(row1);

            DataRow row2 = mTable.NewRow();
            row2["IntVal"] = 2;
            row2["StrVal"] = "error";
            mTable.Rows.Add(row2);

        }

        #endregion

        #region Properties
        public DataTable Table
        {
            get { return mTable; }
            set { mTable = value; }
        }

        public DataView View
        {
            get { return mTable.DefaultView; }
        }
        #endregion

        #region Functions
        #endregion

        #region Enums
        #endregion
    }
}

Step 4 - Create a ViewModel that implements INotifyPropertyChanged.

So creating a ViewModel is not exactly required but there really is benefit to the Model-View-ViewModel design pattern, so we attempt to follow it even though this is a simple example application.

  1. We create a new class called DataViewModel.
  2. We implement the INotifyPropertyChanged interface: first, by adding it as an interface to our object inherits on line 7, and second by adding the functions that interface requires starting on line 44. (though for this small application it isn't used, I don't want to leave it out cause you might need it for your application.)
  3. We changed the constructor to take a Data object we designed in the previous step in as a paramter. (Line 18)
  4. We expose the Table and the Table's view as properties.
DataViewModel.cs
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
using System;
using System.ComponentModel;
using System.Data;

namespace DataGridAddButtonAndImageColumns
{
    public class DataViewModel : INotifyPropertyChanged
    {
        #region Member Variables
        readonly Data mData;
        #endregion

        #region Constructors
        /*
         * The default constructor
         */
        public DataViewModel(Data inData)
        {
            mData = inData;
        }
        #endregion

        #region Properties
        public DataView View
        {
            get { return mData.View; }
        }

        public DataTable Table
        {
            get { return mData.Table; }
        }
        #endregion

        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}

Step 5 - Add code to pass the DataTable to the DataGrid

So in the Window1.xaml.cs file, we create a new DataViewModel object and pass it a new Data object (line 12).

We then use the property in the DataViewModel to pass the DataTable to the DataGrid's DataContext property (line 20).

So now the code behind looks as follows:

Window1.xaml.cs
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
using System.Windows;
using System.Windows.Controls;

namespace DataGridAddButtonAndImageColumns
{
    public partial class Window1 : Window
    {
        #region Contructor
        public Window1()
        {
            InitializeComponent();
            DataViewModel model = new DataViewModel(new Data());

            // It is ok to pass either the DataTable or the DataView
            // so both lines below work, however I am only using one:
            //
            // mDataGrid.DataContext = model.View;
            // mDataGrid.DataContext = model.Table;

            mDataGrid.DataContext = model.Table;
        }
        #endregion

        #region Functions
        private void ButtonFixThis_Click(object sender, RoutedEventArgs e)
        {
            // Do something here
        }
        #endregion
    }
}

Now we can compile and run see our simple app and it should display a DataGrid with the data from the DataTable. It will look something like this:

IntVal StrVal
0 normal
1 warning
2 error

Step 6 - Create the DataTemplateSelectors

A DataTemplateSelector is used by the DataGridTemplateColumn. It is the object that each DataRow will be passed to when the DataGridTemplateColumn is added to the DataGrid.

Since we are going to add one column of images and a second column of buttons, we create two classes that each inherit DataTemplateSelector.

Before we create them, we want them to share a base class, so first, we are going to create a base class for them to share. We are not just creating a base class to follow a "best practice" but we are doing this because both are going to share a function that returns the parent Window1 object. Alternately, we could copy the same function each DataTemplateSelector but what if we update the function and forget it exists in two places? So it is good to have code only in one places.

Ok, lets step through creating this object.

  1. We create a new class and name it BaseDataTemplateSelector.
  2. We inherit DataTemplateSelector in line 7.
  3. We add the function to return the parent Window1 object starting at line 19. This function basically loops through the parent objects until it finds one that is a Window1 object and once found it returns it.
BaseDataTemplateSelector.cs
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace DataGridAddButtonAndImageColumns
{
    public class BaseDataTemplateSelector : DataTemplateSelector
    {
        #region Constructors
        /*
		 * The default constructor
 		 */
        public BaseDataTemplateSelector()
        {
        }
        #endregion

        #region Functions
        protected Window1 GetWindow1(DependencyObject inContainer)
        {
            DependencyObject c = inContainer;
            while (true)
            {
                DependencyObject p = VisualTreeHelper.GetParent(c);

                if (c is Window1)
                {
                    //mSectionControl = c;
                    return c as Window1;
                }
                else
                {
                    c = p;
                }
            }
        }
        #endregion
    }
}

Now that we have a base class, we create an ActionDataTemplateSelector class for choosing whether to add a "Fix" button to each row.

We also add a StatusImageDataTemplateSelector to choose the correct image to display for each row.

We have the ActionDataTemplateSelector override the SelectTemplate function (line 18). The SelectTemplate is what is passed each DataRowView when we add a DataGridTemplateColumn. So in order to control the changes we make to the cells in our new column, we need to overload this function.

Here is how We create the ActionDataTemplateSelector.

  1. We create a new class and name it ActionDataTemplateSelector.
  2. We have the class inherit the BaseDataTemplateSelector (line 7) that we created earlier. It does not have to inherit DataTemplateSelector because the base class already inherits that.
  3. We override the SelectTemplate function starting in line 18.
  4. We cast the oject passed in as the first parameter to a DataRowView in line 21. We know the oject is a DataRowView because when the DataGridTemplateColumn is created, for each DataRowView, the SelectTemplate will be called. The object passed into the SelectTemplate function will be the DataRowView.
  5. We use the GetWindow1 function to find our parent Window1 object because we need a referece to it because it holds our DataTemplates.
  6. We get the value from the DataRowView that is contained in the IntVal column and use that to determine whether to add a button or not. (See the if statement in line 28)
  7. If the column is a warning or error column, we return the DataTemplate for the fix button and the cell in that row will hold and display the button.
ActionDataTemplateSelector.cs
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
using System.Data;
using System.Windows;

namespace DataGridAddButtonAndImageColumns
{
    public class ActionDataTemplateSelector : BaseDataTemplateSelector
    {
        #region Constructors
        /*
		 * The default constructor
 		 */
        public ActionDataTemplateSelector()
        {
        }
        #endregion

        #region Functions
        public override DataTemplate 
        SelectTemplate(object inItem, DependencyObject inContainer)
        {
            DataRowView row = inItem as DataRowView;

            if (row != null)
            {
                Window1 w = GetWindow1(inContainer);
                if (row.DataView.Table.Columns.Contains("IntVal"))
                {
                    if ((int)row["IntVal"] > 0)
                    {
                        return (DataTemplate)w.FindResource("FixThisTemplate");
                    }
                }
                return (DataTemplate)w.FindResource("NormalTemplate");
            }
            return null;
        }
        #endregion
    }
}

The StatusImageDataTemplateSelector also overloads the SelectTempate function and selects the correct image for the status in much the same way that the above does, so we won't re-explain each step.

We do more calculations as each status *normal, warning, error) gets a different image, so I have three if statements starting at line 28.

StatusImageDataTemplateSelector .cs
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
using System.Data;
using System.Windows;

namespace DataGridAddButtonAndImageColumns
{
    public class StatusImageDataTemplateSelector : BaseDataTemplateSelector
    {
        #region Constructors
        /*
		 * The default constructor
 		 */
        public StatusImageDataTemplateSelector()
        {
        }
        #endregion

        #region Functions
        public override DataTemplate SelectTemplate(object inItem, DependencyObject inContainer)
        {
            DataRowView row = inItem as DataRowView;

            if (row != null)
            {
                if (row.DataView.Table.Columns.Contains("IntVal"))
                {
                    Window1 w = GetWindow1(inContainer);
                    int status = (int)row["IntVal"];
                    if (status == 0)
                    {
                        return (DataTemplate)w.FindResource("StatusTemplateNormal");
                    }
                    if (status == 1)
                    {
                        return (DataTemplate)w.FindResource("StatusTemplateWarning");
                    }
                    if (status == 2)
                    {
                        return (DataTemplate)w.FindResource("StatusTemplateError");
                    }
                }
            }
            return null;
        }
        #endregion
    }
}

Step 7 - Create functions that add the new columns and have the constructor call each function.

Now that we have our tools in place to modify each row when we add a column, we can add code to create the two new columns. This is done in the Window1.xaml.cs.

Each function will have four lines:

  1. Create a new DataGridTemplateColumn.
  2. Assign a string for the Header. This string will be what shows up as the column header.
  3. Create a new instance of each DataTemplateSelector object we created (the ActionDataTemplateSelector and the StatusImageDataTemplateSelector) and assign it to the DataGridTemplateColumn's CellTemplateSelector.
  4. Add/Insert the new DataGridTemplateColumn to the DataGrid.

Below you will see the two functions that should be added to the Window1.xaml.cs to preform the steps described

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
public void CreateActionButtonColumn()
{
    DataGridTemplateColumn actionColumn = new DataGridTemplateColumn { CanUserReorder = false, Width = 85, CanUserSort = true };
    actionColumn.Header = "Action";
    actionColumn.CellTemplateSelector = new ActionDataTemplateSelector();
    mDataGrid.Columns.Add(actionColumn);
}

public void CreateStatusColumnWithImages()
{
    DataGridTemplateColumn statusImageColumn = new DataGridTemplateColumn { CanUserReorder = false, Width = 85, CanUserSort = false };;
    statusImageColumn.Header = "Image";
    statusImageColumn.CellTemplateSelector = new StatusImageDataTemplateSelector();
    mDataGrid.Columns.Insert(0, statusImageColumn);
}

Don't forget to call the functions in the constructor. See the two calls in lines 13 and 14.

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
public Window1()
{
    InitializeComponent();
    DataViewModel model = new DataViewModel(new Data());

    // It is ok to pass either the DataTable or the DataView
    // so both lines below work, however I am only using one:
    //
    // mDataGrid.DataContext = model.View;
    // mDataGrid.DataContext = model.Table;

    mDataGrid.DataContext = model.Table;
    CreateActionButtonColumn();
    CreateStatusColumnWithImages();
}

Ok, so now you are finished. This should be working for you if you compile and run the program.

Run it in debug mode and you will see it move through each object that was created as it runs through the code.

You should now have your desired output and you should now understand how to add columns with dynamic data or controls to a DataGrid.

Image IntVal StrVal Action
0 normal  
1 warning <button>Fix</button>
2 error <button>Fix</button>

Options for handling the images without using a static path

The images were called statically in the above example, however, that will be problematic in actual implementation as each program is installed in a different location and the install location can usually be chosen by a user.

You have two options to resolve this, and we will show you how to do both:

  1. Embedding your images
  2. Using image files located in a relative path

Either option works. The second option makes branding a little easier as code doesn't have to be recompiled with new images to change the images, because the image files can simply be replaced.

Embedding your images

So you can embed your images as resources and use the embedded resources instead. To embed them, do this:

  1. In Visual Studio under your project, create a folder called Images.
  2. Copy your images into that folder.
  3. In the XAML, change each of the image resource lines as shown
	<Image Width="16" Height="16" Source="Images\Warning.png" />
Using image files located in a relative path

I decided to NOT embed my images but instead solve this by using a relative path. My preference is for the images to come from actual files in an images directory that is relative to the directory from which the executable is launched:

\MyFolder\
\MyFolder\program.exe
\MyFolder\Images\
\MyFolder\Images\Normal.png
\MyFolder\Images\Warning.png
\MyFolder\Images\Error.png 

So in order to use relative paths, we create another object that inherits IValueConverter.

Here is what we do to create this:

  1. Create a new class called PathConverter.
  2. Make it inherit IValueConverter. (Line 7)
  3. Implement the IValueConverter interface's required methods (line 19 and 45).
  4. Add code to the Convert function. We only are using the Convert method and have no need to use the ConvertBack method, so we will leave it not implemented.
  5. Cast the value parameter to a DataRowView. This should be familiar to you now as it works very similar to the DataTemplateSelector. Each DataRowView will be passed in as the first object parameter.
  6. Get the relative path, (the path in which your executable was launched) using System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location).
  7. Get the status value from the DataRowView and add a couple of if statements that return the relative path + the image file path.
PathConverter.cs
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
using System.Data;
using System.Globalization;
using System.Windows.Data;

namespace DataGridAddButtonAndImageColumns
{
    public class PathConverter : IValueConverter
    {
        #region Constructors
        /*
		 * The default constructor
 		 */
        public PathConverter()
        {
        }
        #endregion

        #region IValueConverter Members
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DataRowView row = value as DataRowView;
            if (row != null)
            {
                if (row.DataView.Table.Columns.Contains("IntVal"))
                {
                    String workingDirectory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
                    int status = (int)row["IntVal"];
                    if (status == 0)
                    {
                        return workingDirectory + @"\Normal.png";
                    }
                    if (status == 1)
                    {
                        return workingDirectory + @"\Warning.png";
                    }
                    if (status == 2)
                    {
                        return workingDirectory + @"\Error.png";
                    }
                }
            }
            return null;
        }

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

Ok, we are not done yet. We now needed to edit the XAML again. We don't have an instance of our PathConverter object. We can create an instance of it in the Windows.Resources section of the XAML.

Once created, we can change the Image objects in each DataTemplate to bind to a converter and assign the converter to the PathConverter object we instantiated int the XAML.

Here are the steps.

  1. We add an xmlns reference to load the local namespace. (Line 5)
  2. We add in the Windows.Resources and instance of the PathConverter.
  3. We change the Image Source value to:
    Source="{Binding Converter={StaticResource ImagePathConverter}}"
     
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
<Window x:Class="DataGridAddButtonAndImageColumns.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:wpftk="http://schemas.microsoft.com/wpf/2008/toolkit"
    xmlns:local="clr-namespace:DataGridAddButtonAndImageColumns"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <local:PathConverter x:Key="ImagePathConverter" />
        <DataTemplate x:Key="FixThisTemplate">
            <Button Name="mButtonFixThis" Click="ButtonFixThis_Click">Fix This</Button>
        </DataTemplate>
        <DataTemplate x:Key="NormalTemplate">
        </DataTemplate>
        <DataTemplate x:Key="StatusTemplateNormal" x:Name="mNormalImage">
            <Image Width="16" Height="16" Margin="3,0" Source="{Binding Converter={StaticResource ImagePathConverter}}" />
            <!--<Image Width="16" Height="16" Source="Images\Normal.png" />--><!-- Embedded -->
        </DataTemplate>
        <DataTemplate x:Key="StatusTemplateWarning" x:Name="mWarningImage">
            <Image Width="16" Height="16" Margin="3,0" Source="{Binding Converter={StaticResource ImagePathConverter}}" />
            <!--<Image Width="16" Height="16" Source="Images\Warning.png" />--><!-- Embedded -->
        </DataTemplate>
        <DataTemplate x:Key="StatusTemplateError" x:Name="mErrorImage">
            <Image Width="16" Height="16" Margin="3,0" Source="{Binding Converter={StaticResource ImagePathConverter}}" />
            <!--<Image Width="16" Height="16" Source="Images\Error.png" />--><!-- Embedded -->
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <wpftk:DataGrid Name="mDataGrid" ItemsSource="{Binding}" CanUserAddRows="False" IsReadOnly="True"></wpftk:DataGrid>
    </Grid>
</Window>

Ok, now we should be done. 

Make sure to create the Images folder and add the images in the location where you exectuable runs. You may have to add the images folder to both the debug and release directories or otherwise resolve this, else you will get an exception when the images are not found.

 

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here