经过长时间的讨论,我想澄清一些事情。我在下面的解决方案与 Mike Paisner 的回答非常相似,我对他的解决方案表示赞赏。干得好……给我点赞。
关于使用操作系统的复制命令,它会根据您的需要而工作。在 Mike Paisner 的原始问题中,代码使用操作系统的复制命令来复制网格中的“选定”单元格。使用操作系统的复制命令时,粘贴会覆盖未选择“空”值的单元格。
考虑到操作系统如何使用在DataGridView 中选择的单元格构建复制命令,这是正确的。使用原始帖子中发布的代码,它如上所述工作,用空值覆盖未选择的单元格。如果您想“忽略”空值而不覆盖这些单元格,那么简单检查原始代码即可解决此问题。像……
if (col < ws.ColumnCount && row < ws.RowCount) {
if (cells[i].ToString() != "") { // <-- added check for empty (non-selected) cells
ws.Rows[row].Cells[col].Value = cells[i].ToString();
}
else {
// empty cell value
}
}
这有效并且将停止覆盖未选中的单元格。但是,如前所述,如果用户选择了一个“空”单元格然后单击复制按钮,选择另一个单元格,然后单击粘贴按钮,则要粘贴到的单元格中的任何值都将保持不变。如果这是所需的行为,那么代码就完成了,但是,恕我直言,如果我选择了一个空单元格并复制粘贴它,那么它不应该仅仅因为它是一个空值而忽略粘贴的值。
如果您想在选中空单元格时复制它们,那么使用操作系统的复制命令将不起作用。很难区分空的“选中”单元格和“未选中”单元格。请理解,我不想劝阻任何人使用操作系统的复制命令,在许多情况下这是最好的方法。但是,在 OP 的情况下,这将不起作用,因为他们希望能够粘贴“空”单元格。
鉴于此,我的建议是使用DataGridView 的SelectedCells 集合来管理粘贴过程。使用这个集合需要不同的方法和更多的工作。对于初学者,如 cmets 中所述,当用户选择要粘贴到的单元格时,SelectedCells 集合将使用我们要粘贴到的选定单元格重新开始。因此,删除我们需要的先前选择的单元格。我建议在单击复制按钮时使用全局变量来存储选定的单元格。然后当点击粘贴按钮时,我们可以使用保存先前选择的单元格的全局变量。
The SelectedCells collection is a single dimension “dynamic” array and obviously grows/shrinks when cells are selected/deselected.从技术上讲,集合是一个“堆栈”或 FIFO(先进先出)……集合中的“第一个”单元格是选择的“最后一个”单元格,集合中的“最后一个”单元格是选择的“第一个”单元格.这显然使您能够知道选择单元格的顺序。这是设计使然并且有意义,但是对于这种情况,选择单元格的顺序无关紧要。与操作系统副本相同,我们只关心由选定单元格创建的矩形的“左上”单元格的位置。
因为单元格的排序顺序与用户选择单元格的顺序相同,所以我们需要找到矩形选择的“左上角”单元格(浅绿色单元格 R1C1)。例子;使用下图,使用 Ctrl 键以 col2、col1、col4 和 col3 的顺序多选单元格进行选择。
选定的单元格数组看起来像……
Index cellValue
0 R3C3
1 R1C4
2 R3C1
3 R5C2
在这个例子中,我们想要找到集合中编号“最低”的列和行。我们可以遍历集合,或者,由于集合是可枚举的,我们可以简单地根据列索引对集合进行排序,然后获取第一项列索引。然后按行索引对其进行排序并获取第一项行索引。它可能看起来像……
int colIndex = copyCollection.Cast<DataGridViewCell>().OrderBy(c => c.ColumnIndex).First().ColumnIndex;
int rowIndex = copyCollection.Cast<DataGridViewCell>().OrderBy(c => c.RowIndex).First().RowIndex;
这将为我们提供选择矩形(图片中的粉红色矩形)中左上角的单元格(浅绿色单元格)。在此示例中,第一列将是第 1 列,顶行将是第 1 行。假设用户单击“R5C0”单元格(深绿色)进行粘贴。
现在我们想要获得需要添加到每个选定单元格的行和列索引的“神奇”数字。看图片,这种差异将是两个绿色单元格之间的差异。这可能看起来像……
int colDif = ws.CurrentCell.ColumnIndex - colIndex
int rowDif = ws.CurrentCell.RowIndex - rowIndex;
那么colDif 将是 0 – 1 = -1 ...rowDif 将是 5 – 1 = 4。这些将是我们需要“添加”到所选单元格集合中的每个单元格的“神奇”数字映射到要粘贴到的正确单元格。
示例:在选定单元格循环中,我们需要每个选定单元格的新列和行索引,在这种情况下(从上面)集合中的第一个单元格是“R3C3”,因此它的新列索引将是它的当前索引加上difCol 值。 ... 3 + (-1) = 2,与行相同... 3 + 4 = 7。这会将单元格“R3C3”粘贴到单元格“R7C2”中。一个简单的检查是查看“R1C1”并检查与“R3C3”的差异……右侧两列,向下两列。所以,如果我们从粘贴单元格“R5C0”开始,那么 5 + 2 = 7 和 0 + 2 = 2……我希望这是有道理的。
左上角选定单元格矩形与粘贴单元格位置的差值可用于所有选定单元格,并且它应该正确映射而无需大量索引杂耍。
最后,下面的代码。对于粘贴过程,会检查空/空选择并返回。然后我们得到“神奇的”差异值以添加到每个选定的单元格。循环遍历创建映射行/列索引的选定单元格。进行边界检查以忽略网格边界之外的单元格,并检查新行。如果存在,这将防止粘贴到新行中。如果所有条件都成功,则粘贴单元格。
我为代码中的“选定”和“粘贴”单元格添加了单元格颜色,以直观地帮助测试这一点。我希望我做对了。
DataGridViewSelectedCellCollection copyCollection;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
FillGrid();
}
private void btnCopy_Click(object sender, EventArgs e) {
ClearCellColors();
copyCollection = ws.SelectedCells;
foreach (DataGridViewCell cell in copyCollection) {
cell.Style.BackColor = Color.LightCoral;
}
}
private void btn_Paste_Click(object sender, EventArgs e) {
if (copyCollection == null || copyCollection.Count == 0) {
Debug.WriteLine("Copy button was not clicked - empty collection");
return;
}
// get the difference between the top-left columns and rows
int colDif = ws.CurrentCell.ColumnIndex - copyCollection.Cast<DataGridViewCell>().OrderBy(c => c.ColumnIndex).First().ColumnIndex;
int rowDif = ws.CurrentCell.RowIndex - copyCollection.Cast<DataGridViewCell>().OrderBy(c => c.RowIndex).First().RowIndex;
int rowIndex;
int colIndex;
foreach (DataGridViewCell cell in copyCollection) {
rowIndex = cell.RowIndex + rowDif;
colIndex = cell.ColumnIndex + colDif;
if (rowIndex >= 0 && colIndex >= 0 &&
rowIndex < ws.RowCount && colIndex < ws.ColumnCount &&
!ws.Rows[rowIndex].IsNewRow) {
ws.Rows[rowIndex].Cells[colIndex].Value = cell.Value;
ws.Rows[rowIndex].Cells[colIndex].Style.BackColor = Color.LightGreen;
}
else {
Debug.WriteLine("Out of bounds: RowIndex:" + rowIndex + " ColIndex: " + colIndex);
}
}
}
private void FillGrid() {
DataGridViewTextBoxColumn newCol;
for (int i = 0; i < 5; i++) {
newCol = new DataGridViewTextBoxColumn();
newCol.Name = "Col " + i;
ws.Columns.Add(newCol);
}
for (int row = 0; row < 20; row++) {
int curRowIndex = ws.Rows.Add();
for (int col = 0; col < ws.Columns.Count; col++) {
ws.Rows[curRowIndex].Cells[col].Value = "R" + curRowIndex + "C" + col;
}
}
}
private void ClearCellColors() {
foreach (DataGridViewRow row in ws.Rows) {
foreach (DataGridViewCell cell in row.Cells) {
cell.Style.BackColor = Color.White;
}
}
}
我希望这是有道理的并有所帮助。 ?