Introduction
Update - See restriction 3 below.
Using the standard DataGrid
that was added with .NET 4, I found that I needed to implement copy and paste. Searching the internet came up with solutions
that either worked with Silverlight or with some sort of pre-release version that you could add to Visual Studio 2008 (.NET 3).
To my horror, I had to actually figure something out and write some code. I'm very new to WPF and have spent the last few months copying example code off the internet
and modifying it for my use but I think I have finally written something that might be of use to other people, so here it is.
Credit goes to the following two pages that helped, and some code was taken from the second one (credited in comments):
The solution allows you to do some limited copy and pasting between cells. In the example, I have a table of people with fields first name,
second name, and legs (a number - usually 2). In the application I was working on, it made no sense to copy from one column to another.
So in this example, you can't copy the legs column to the first name column (for example). This is intentional and the restriction could be removed easily by deleting some checks.
Restrictions / problems to solve!
- It only deals with strings or integers. It should be easy to expand on the types allowed with more code but perhaps that can be avoided completely with a different implementation.
- The clipboard is only converted from one particular format. I need to see if copying from Excel works - otherwise I probably need to put some code
back into
DatagridHelper
that I removed!
- Important If the rows are re-ordered, it breaks this code! For this reason, I suggest you don't use this code.
Reto Ravisio saw this article and provided
a much better solution. I have taken that, changed it slightly, and provided a complete solution file that uses his code:
Hopefully this will help beginners like myself use that code.
Using the code
I will go over some of the code you have to copy into your project, but you'll have to see the entire solution to get all the required code.
This is a tester to show you the main principles so you can decide if it's for you.
The DataGrid
is declared like this:
<DataGrid Name="dataGrid1" ItemsSource="{Binding PeopleList}"
SelectionUnit="Cell"
AutoGenerateColumns="True"
KeyDown="dataGrid1_KeyDown"
CopyingRowClipboardContent="dataGrid1_CopyingRowClipboardContent"
ColumnReordered="dataGrid1_ColumnReordered"/>
Note that the SelectionUnit
is set to Cell
. This allows individual cells to be selected rather than the whole row. The items are taken from
an ObserverableCollection
and the columns are automatically generated.
The KeyDown
event is set to detect the paste operation:
void dataGrid1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.V &&
(Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
if (ValidatePaste() == false)
return;
viewModel.Paste(dataGrid1.CurrentColumn.DisplayIndex,
GetTargetRow(),
dataGrid1.SelectedCells.Count,
Clipboard.GetData(DataFormats.Text) as string);
}
}
As you can see, a View Model is used.
The View Model Paste
function uses the ClipboardHelper
class (taken from the MSDN blog mentioned in the introduction) to convert
the string version of the copied cells into a list of string arrays representing the cells on each row. Then it loops through the target rows setting the ObservableCollection
.
Reflection is used to set the properties in the collection having only a string representing the property name. This avoids having a switch
statement checking
the display index value and matching that up to a property. It would have made the code very tedious to write and not easily re-usable.
The SetProperty
function implements that functionality:
private void SetProperty(Person person, string propertyName, string cellItem)
{
Type type = person.GetType();
PropertyInfo prop = type.GetProperty(propertyName);
if (prop == null)
{
Debug.WriteLine("Property not found: " + propertyName);
return;
}
if (prop.PropertyType == typeof(string)) prop.SetValue(person, cellItem, null);
else
prop.SetValue(person, int.Parse(cellItem), null);
person.OnPropertyChanged(propertyName);
}
You can see in the code above where the restriction on having only strings or integers comes from.
The People
class is a simple container class that implements INotifyPropertyChanged
:
class Person : INotifyPropertyChanged
{
public Person(string first, string second)
{
FirstName = first;
SecondName = second;
Legs = 2;
}
public string FirstName { set; get; }
public string SecondName { set; get; }
public int Legs { set; get; }
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
The code which populates the map of property names and display indexes (used in the VM Paste
function) relies on this bit of code in the code-behind:
private void dataGrid1_ColumnReordered(object sender, DataGridColumnEventArgs e)
{
viewModel.ClearColumnMapping();
foreach (DataGridColumn col in dataGrid1.Columns)
{
viewModel.AddColumnMapping(col.DisplayIndex, col.SortMemberPath);
}
}
I'm assuming that the SortMemberPath
will always be the same as the property name in the People
class. It was in my case.
History
- 2011-09-01: A new version which gets the column information when the columns are re-ordered rather than when loaded.
Updated a downloadable complete solution based on Reto's article.