Спасибо,
Сообщение очень хорошее от Винсента, но KlausG добавил в него некоторые исправления, которые также следует учитывать при работе с FrameWork 4.0.Очень важно
Оригинальная публикация на веб-сайте Vincent: http://blogs.msdn.com/b/vinsibal/archive/2008/09/25/pasting-content-to-new-rows-on-the-wpf-datagrid.aspx
Примечание. Если вы не можете добавить строку, убедитесь, что у вас есть конструктор по умолчанию для вашего элемента.
Обновлено 2018-03-06 (для поддержки DataGrid с переупорядоченными пользователем столбцами)
Использование
<myControl:CustomDataGrid ...
CustomDataGrid
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace HQ.Wpf.Util.MyControl
{
public class CustomDataGrid : DataGrid
{
public event ExecutedRoutedEventHandler ExecutePasteEvent;
public event CanExecuteRoutedEventHandler CanExecutePasteEvent;
// ******************************************************************
static CustomDataGrid()
{
CommandManager.RegisterClassCommandBinding(
typeof(CustomDataGrid),
new CommandBinding(ApplicationCommands.Paste,
new ExecutedRoutedEventHandler(OnExecutedPasteInternal),
new CanExecuteRoutedEventHandler(OnCanExecutePasteInternal)));
}
// ******************************************************************
#region Clipboard Paste
// ******************************************************************
private static void OnCanExecutePasteInternal(object target, CanExecuteRoutedEventArgs args)
{
((CustomDataGrid)target).OnCanExecutePaste(target, args);
}
// ******************************************************************
/// <summary>
/// This virtual method is called when ApplicationCommands.Paste command query its state.
/// </summary>
/// <param name="args"></param>
protected virtual void OnCanExecutePaste(object target, CanExecuteRoutedEventArgs args)
{
if (CanExecutePasteEvent != null)
{
CanExecutePasteEvent(target, args);
if (args.Handled)
{
return;
}
}
args.CanExecute = CurrentCell != null;
args.Handled = true;
}
// ******************************************************************
private static void OnExecutedPasteInternal(object target, ExecutedRoutedEventArgs args)
{
((CustomDataGrid)target).OnExecutedPaste(target, args);
}
// ******************************************************************
/// <summary>
/// This virtual method is called when ApplicationCommands.Paste command is executed.
/// </summary>
/// <param name="target"></param>
/// <param name="args"></param>
protected virtual void OnExecutedPaste(object target, ExecutedRoutedEventArgs args)
{
if (ExecutePasteEvent != null)
{
ExecutePasteEvent(target, args);
if (args.Handled)
{
return;
}
}
// parse the clipboard data [row][column]
List<string[]> clipboardData = HQ.Util.General.Clipboard.ClipboardHelper2.ParseClipboardData();
bool hasAddedNewRow = false;
Debug.Print(">>> DataGrid Paste: >>>");
#if DEBUG
StringBuilder sb = new StringBuilder();
#endif
int minRowIndex = Items.IndexOf(CurrentItem);
int maxRowIndex = Items.Count - 1;
int startIndexOfDisplayCol = (SelectionUnit != DataGridSelectionUnit.FullRow) ? CurrentColumn.DisplayIndex : 0;
int clipboardRowIndex = 0;
for (int i = minRowIndex; i <= maxRowIndex && clipboardRowIndex < clipboardData.Count; i++, clipboardRowIndex++)
{
if (i < this.Items.Count)
{
CurrentItem = Items[i];
BeginEditCommand.Execute(null, this);
int clipboardColumnIndex = 0;
for (int j = startIndexOfDisplayCol; clipboardColumnIndex < clipboardData[clipboardRowIndex].Length; j++, clipboardColumnIndex++)
{
// DataGridColumn column = ColumnFromDisplayIndex(j);
DataGridColumn column = null;
foreach (DataGridColumn columnIter in this.Columns)
{
if (columnIter.DisplayIndex == j)
{
column = columnIter;
break;
}
}
column?.OnPastingCellClipboardContent(Items[i], clipboardData[clipboardRowIndex][clipboardColumnIndex]);
#if DEBUG
sb.AppendFormat("{0,-10}", clipboardData[clipboardRowIndex][clipboardColumnIndex]);
sb.Append(" - ");
#endif
}
CommitEditCommand.Execute(this, this);
if (i == maxRowIndex)
{
maxRowIndex++;
hasAddedNewRow = true;
}
}
Debug.Print(sb.ToString());
#if DEBUG
sb.Clear();
#endif
}
// update selection
if (hasAddedNewRow)
{
UnselectAll();
UnselectAllCells();
CurrentItem = Items[minRowIndex];
if (SelectionUnit == DataGridSelectionUnit.FullRow)
{
SelectedItem = Items[minRowIndex];
}
else if (SelectionUnit == DataGridSelectionUnit.CellOrRowHeader ||
SelectionUnit == DataGridSelectionUnit.Cell)
{
SelectedCells.Add(new DataGridCellInfo(Items[minRowIndex], Columns[startIndexOfDisplayCol]));
}
}
}
// ******************************************************************
/// <summary>
/// Whether the end-user can add new rows to the ItemsSource.
/// </summary>
public bool CanUserPasteToNewRows
{
get { return (bool)GetValue(CanUserPasteToNewRowsProperty); }
set { SetValue(CanUserPasteToNewRowsProperty, value); }
}
// ******************************************************************
/// <summary>
/// DependencyProperty for CanUserAddRows.
/// </summary>
public static readonly DependencyProperty CanUserPasteToNewRowsProperty =
DependencyProperty.Register("CanUserPasteToNewRows",
typeof(bool), typeof(CustomDataGrid),
new FrameworkPropertyMetadata(true, null, null));
// ******************************************************************
#endregion Clipboard Paste
private void SetGridToSupportManyEditEitherWhenValidationErrorExists()
{
this.Items.CurrentChanged += Items_CurrentChanged;
//Type DatagridType = this.GetType().BaseType;
//PropertyInfo HasCellValidationProperty = DatagridType.GetProperty("HasCellValidationError", BindingFlags.NonPublic | BindingFlags.Instance);
//HasCellValidationProperty.
}
void Items_CurrentChanged(object sender, EventArgs e)
{
//this.Items[0].
//throw new NotImplementedException();
}
// ******************************************************************
private void SetGridWritable()
{
Type DatagridType = this.GetType().BaseType;
PropertyInfo HasCellValidationProperty = DatagridType.GetProperty("HasCellValidationError", BindingFlags.NonPublic | BindingFlags.Instance);
if (HasCellValidationProperty != null)
{
HasCellValidationProperty.SetValue(this, false, null);
}
}
// ******************************************************************
public void SetGridWritableEx()
{
BindingFlags bindingFlags = BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Instance;
PropertyInfo cellErrorInfo = this.GetType().BaseType.GetProperty("HasCellValidationError", bindingFlags);
PropertyInfo rowErrorInfo = this.GetType().BaseType.GetProperty("HasRowValidationError", bindingFlags);
cellErrorInfo.SetValue(this, false, null);
rowErrorInfo.SetValue(this, false, null);
}
// ******************************************************************
}
}
Также: Помощник буфера обмена:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Windows;
using System.IO;
namespace HQ.Util.General.Clipboard
{
public static class ClipboardHelper
{
public delegate string[] ParseFormat(string value);
public static List<string[]> ParseClipboardData()
{
List<string[]> clipboardData = null;
object clipboardRawData = null;
ParseFormat parseFormat = null;
// get the data and set the parsing method based on the format
// currently works with CSV and Text DataFormats
IDataObject dataObj = System.Windows.Clipboard.GetDataObject();
if ((clipboardRawData = dataObj.GetData(DataFormats.CommaSeparatedValue)) != null)
{
parseFormat = ParseCsvFormat;
}
else if((clipboardRawData = dataObj.GetData(DataFormats.Text)) != null)
{
parseFormat = ParseTextFormat;
}
if (parseFormat != null)
{
string rawDataStr = clipboardRawData as string;
if (rawDataStr == null && clipboardRawData is MemoryStream)
{
// cannot convert to a string so try a MemoryStream
MemoryStream ms = clipboardRawData as MemoryStream;
StreamReader sr = new StreamReader(ms);
rawDataStr = sr.ReadToEnd();
}
Debug.Assert(rawDataStr != null, string.Format("clipboardRawData: {0}, could not be converted to a string or memorystream.", clipboardRawData));
string[] rows = rawDataStr.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
if (rows != null && rows.Length > 0)
{
clipboardData = new List<string[]>();
foreach (string row in rows)
{
clipboardData.Add(parseFormat(row));
}
}
else
{
Debug.WriteLine("unable to parse row data. possibly null or contains zero rows.");
}
}
return clipboardData;
}
public static string[] ParseCsvFormat(string value)
{
return ParseCsvOrTextFormat(value, true);
}
public static string[] ParseTextFormat(string value)
{
return ParseCsvOrTextFormat(value, false);
}
private static string[] ParseCsvOrTextFormat(string value, bool isCSV)
{
List<string> outputList = new List<string>();
char separator = isCSV ? ',' : '\t';
int startIndex = 0;
int endIndex = 0;
for (int i = 0; i < value.Length; i++)
{
char ch = value[i];
if (ch == separator)
{
outputList.Add(value.Substring(startIndex, endIndex - startIndex));
startIndex = endIndex + 1;
endIndex = startIndex;
}
else if (ch == '\"' && isCSV)
{
// skip until the ending quotes
i++;
if (i >= value.Length)
{
throw new FormatException(string.Format("value: {0} had a format exception", value));
}
char tempCh = value[i];
while (tempCh != '\"' && i < value.Length)
i++;
endIndex = i;
}
else if (i + 1 == value.Length)
{
// add the last value
outputList.Add(value.Substring(startIndex));
break;
}
else
{
endIndex++;
}
}
return outputList.ToArray();
}
}
}
CsvHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using MoreLinq; // http://stackoverflow.com/questions/15265588/how-to-find-item-with-max-value-using-linq
namespace HQ.Util.General.CSV
{
public class CsvHelper
{
public static Dictionary<LineSeparator, Func<string, string[]>> DictionaryOfLineSeparatorAndItsFunc = new Dictionary<LineSeparator, Func<string, string[]>>();
static CsvHelper()
{
DictionaryOfLineSeparatorAndItsFunc[LineSeparator.Unknown] = ParseLineNotSeparated;
DictionaryOfLineSeparatorAndItsFunc[LineSeparator.Tab] = ParseLineTabSeparated;
DictionaryOfLineSeparatorAndItsFunc[LineSeparator.Semicolon] = ParseLineSemicolonSeparated;
DictionaryOfLineSeparatorAndItsFunc[LineSeparator.Comma] = ParseLineCommaSeparated;
}
// ******************************************************************
public enum LineSeparator
{
Unknown = 0,
Tab,
Semicolon,
Comma
}
// ******************************************************************
public static LineSeparator GuessCsvSeparator(string oneLine)
{
List<Tuple<LineSeparator, int>> listOfLineSeparatorAndThereFirstLineSeparatedValueCount = new List<Tuple<LineSeparator, int>>();
listOfLineSeparatorAndThereFirstLineSeparatedValueCount.Add(new Tuple<LineSeparator, int>(LineSeparator.Tab, CsvHelper.ParseLineTabSeparated(oneLine).Count()));
listOfLineSeparatorAndThereFirstLineSeparatedValueCount.Add(new Tuple<LineSeparator, int>(LineSeparator.Semicolon, CsvHelper.ParseLineSemicolonSeparated(oneLine).Count()));
listOfLineSeparatorAndThereFirstLineSeparatedValueCount.Add(new Tuple<LineSeparator, int>(LineSeparator.Comma, CsvHelper.ParseLineCommaSeparated(oneLine).Count()));
Tuple<LineSeparator, int> bestBet = listOfLineSeparatorAndThereFirstLineSeparatedValueCount.MaxBy((n)=>n.Item2);
if (bestBet != null && bestBet.Item2 > 1)
{
return bestBet.Item1;
}
return LineSeparator.Unknown;
}
// ******************************************************************
public static string[] ParseLineCommaSeparated(string line)
{
// CSV line parsing : From "jgr4" in http://www.kimgentes.com/worshiptech-web-tools-page/2008/10/14/regex-pattern-for-parsing-csv-files-with-embedded-commas-dou.html
var matches = Regex.Matches(line, @"\s?((?<x>(?=[,]+))|""(?<x>([^""]|"""")+)""|""(?<x>)""|(?<x>[^,]+)),?",
RegexOptions.ExplicitCapture);
string[] values = (from Match m in matches
select m.Groups["x"].Value.Trim().Replace("\"\"", "\"")).ToArray();
return values;
}
// ******************************************************************
public static string[] ParseLineTabSeparated(string line)
{
var matchesTab = Regex.Matches(line, @"\s?((?<x>(?=[\t]+))|""(?<x>([^""]|"""")+)""|""(?<x>)""|(?<x>[^\t]+))\t?",
RegexOptions.ExplicitCapture);
string[] values = (from Match m in matchesTab
select m.Groups["x"].Value.Trim().Replace("\"\"", "\"")).ToArray();
return values;
}
// ******************************************************************
public static string[] ParseLineSemicolonSeparated(string line)
{
// CSV line parsing : From "jgr4" in http://www.kimgentes.com/worshiptech-web-tools-page/2008/10/14/regex-pattern-for-parsing-csv-files-with-embedded-commas-dou.html
var matches = Regex.Matches(line, @"\s?((?<x>(?=[;]+))|""(?<x>([^""]|"""")+)""|""(?<x>)""|(?<x>[^;]+));?",
RegexOptions.ExplicitCapture);
string[] values = (from Match m in matches
select m.Groups["x"].Value.Trim().Replace("\"\"", "\"")).ToArray();
return values;
}
// ******************************************************************
public static string[] ParseLineNotSeparated(string line)
{
string [] lineValues = new string[1];
lineValues[0] = line;
return lineValues;
}
// ******************************************************************
public static List<string[]> ParseText(string text)
{
string[] lines = text.Split(new string[] { "\r\n" }, StringSplitOptions.None);
return ParseString(lines);
}
// ******************************************************************
public static List<string[]> ParseString(string[] lines)
{
List<string[]> result = new List<string[]>();
LineSeparator lineSeparator = LineSeparator.Unknown;
if (lines.Any())
{
lineSeparator = GuessCsvSeparator(lines[0]);
}
Func<string, string[]> funcParse = DictionaryOfLineSeparatorAndItsFunc[lineSeparator];
foreach (string line in lines)
{
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
result.Add(funcParse(line));
}
return result;
}
// ******************************************************************
}
}
ClipboardHelper2.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Windows;
using System.IO;
using System.Linq;
using System.Windows.Forms.VisualStyles;
using HQ.Util.General.CSV;
namespace HQ.Util.General.Clipboard
{
// Uses Clipboard in WPF (PresentationCore.dll in v4 of the framework)
public static class ClipboardHelper2
{
public delegate string[] ParseFormat(string value);
public static List<string[]> ParseClipboardData()
{
List<string[]> clipboardData = new List<string[]>();
// get the data and set the parsing method based on the format
// currently works with CSV and Text DataFormats
IDataObject dataObj = System.Windows.Clipboard.GetDataObject();
if (dataObj != null)
{
string[] formats = dataObj.GetFormats();
if (formats.Contains(DataFormats.CommaSeparatedValue))
{
string clipboardString = (string)dataObj.GetData(DataFormats.CommaSeparatedValue);
{
// EO: Subject to error when a CRLF is included as part of the data but it work for the moment and I will let it like it is
// WARNING ! Subject to errors
string[] lines = clipboardString.Split(new string[] { "\r\n" }, StringSplitOptions.None);
string[] lineValues;
foreach (string line in lines)
{
lineValues = CsvHelper.ParseLineCommaSeparated(line);
if (lineValues != null)
{
clipboardData.Add(lineValues);
}
}
}
}
else if (formats.Contains(DataFormats.Text))
{
string clipboardString = (string)dataObj.GetData(DataFormats.Text);
clipboardData = CsvHelper.ParseText(clipboardString);
}
}
return clipboardData;
}
}
}