我已经实现了一个C# DataGridView,并构建了复制/粘贴功能,如其他帖子中所述:
// Copy Code
DataObject d = ws.GetClipboardContent();
if (d != null)
{
Clipboard.SetDataObject(d);
}//
// Paste Code
string s = Clipboard.GetText();
string sWithoutSlashR = s.Replace("\r", ""); // get rid of \r if it exists
string[] lines = sWithoutSlashR.Split('\n');
int row = ws.CurrentCell.RowIndex;
Boolean allGood = true;
foreach (string line in lines)
{
int col = ws.CurrentCell.ColumnIndex;
string[] cells = line.Split('\t');
int cellsSelected = cells.Length;
for (int i = 0; i < cellsSelected; i++)
{
if (col < ws.ColumnCount && row < ws.RowCount)
{
ws.Rows[row].Cells[col].Value = cells[i].ToString();
}
col++;
}
row++;
}如果复制源单元格中有空白,而粘贴目标单元格不为空,则会出现问题。假设要复制三个内容为"T“、"”、"T“的单元格(即引号只是为了便于阅读-中间的单元格是空白的)。如果用户选择所有三个单元格,则复制的内容为:"T\r\n\r\nT“。粘贴此内容会用空白覆盖中间的单元格,这是可以的。
但是,如果用户仅使用cell键选择第一个和第三个单元格,则复制的内容是相同的:"T\r\n\r\nT“。由于用户没有选择中间的空白单元格,因此不应该粘贴中间的空白单元格,但它确实粘贴了!
复制内容相同,如何区分选中的空单元格和跳过的单元格?
麦克
发布于 2020-07-16 23:11:36
向JohnG喊一声,让我走上正确的方向!以下是我的解决方案:
全局变量:
DataGridViewSelectedCellCollection copySource;在下面的示例中,我的DataGridView名为ws。在通过CTRL+C或上下文菜单访问的复制命令中,我填充了全局变量:
copySource = ws.SelectedCells;粘贴命令如下:
if (copySource == null) return; // nothing to paste, so leave
if (copySource.Count == 0) return; // nothing to paste, so leave
// find top left Source cell
int topSourceRow = copySource[0].RowIndex;
int leftSourceCol = copySource[0].ColumnIndex;
foreach(DataGridViewCell cell in copySource)
{
if (cell.RowIndex < topSourceRow) topSourceRow = cell.RowIndex;
if (cell.ColumnIndex < leftSourceCol) leftSourceCol = cell.ColumnIndex;
}
// find top left destination cell
int topDestRow = ws.SelectedCells[0].RowIndex;
int leftDestCol = ws.SelectedCells[0].ColumnIndex;
// paste cells
Boolean allGood = true;
foreach (DataGridViewCell cell in copySource)
{
// check if we have run off the edge of the grid
// todo: expand if statement to add type checks if needed
if (topDestRow + cell.RowIndex - topSourceRow >= ws.Rows.Count || leftDestCol + cell.ColumnIndex - leftSourceCol >= ws.Columns.Count)
{
// do not paste, set flag to show warning
allGood = false;
}
else
{
// perform paste
ws.Rows[topDestRow + cell.RowIndex - topSourceRow].Cells[leftDestCol + cell.ColumnIndex - leftSourceCol].Value = cell.Value.ToString();
}
}
if (!allGood)
{
// todo: display warning to user that not all cells were pasted
}初始冒烟测试表明,空白单元格被复制并正确覆盖了目标单元格。复制不连续的单元格的行为与预期一样-复制源中间跳过的单元格不包括在粘贴中。
这种方法的假设是用户已经选择了目标单元格的左上角进行粘贴,我认为这是合理的。
如果粘贴离开右边缘或下边缘,则粘贴可以粘贴的单元格。跳过边缘之外的单元格,并向用户显示一条警告消息。您的错误检查要求可能有所不同。
在我的应用程序中,我将保留将源单元格复制到OS剪贴板的原始代码,以便用户可以灵活地从我的应用程序复制到Excel或其他目标位置。
发布于 2020-07-17 15:13:27
经过长时间的讨论,我想澄清一些事情。我下面的解决方案与Mike Paisner的答案非常相似,我给他竖起大拇指寻找解决方案。干得好…我投赞成票。
在参考使用操作系统的copy命令时,它将根据您的需要而工作。在Mike Paisner的原始问题中,代码使用操作系统的复制命令复制网格中的“选定”单元格。当使用操作系统的copy命令时,粘贴将覆盖未用“空”值选择的单元格。
这是真实的,给定OS如何构造具有在DataGridView中选择的单元格的复制命令。使用原始post中的post代码,它的工作方式如上所述,用空值覆盖未选中的单元格。如果您希望“忽略”空值并且不覆盖这些单元格,那么在原始代码中进行简单的检查就可以解决这个问题。像…这样的东西
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
}
}这将起作用,并将停止覆盖未选中的单元格。但是,如前所述,如果用户选择了一个“空”单元格,然后单击复制按钮,选择了另一个单元格,然后单击粘贴按钮,则要粘贴到的单元格中的任何值都将保持不变。如果这是期望的行为,那么代码就完成了,但是,如果我选择了一个空单元格并复制粘贴了它,那么它不应该仅仅因为粘贴的值是空值而忽略它。
如果要在选中空单元格时对其进行复制,则使用操作系统的复制命令将不起作用。很难区分空的“选中”单元格和“未选中”单元格。请理解,我不想劝阻任何人使用操作系统的复制命令,在许多情况下,这是最好的方法。然而,在OPs的情况下,这将不起作用,因为他们希望能够粘贴“空”单元格。
鉴于此,我的建议是使用DataGridView的SelectedCells集合来管理粘贴过程。使用这个集合需要一种不同的方法和更多的工作。首先,正如注释中所指出的,当用户选择要粘贴到的单元格时,SelectedCells集合将从我们想要粘贴到的选定单元格重新开始。因此,删除我们需要的先前选定的单元格。我建议在单击copy按钮时使用全局变量来存储选定的单元格。然后,当单击paste按钮时,我们可以使用保存先前选择的单元格的全局变量。
SelectedCells集合是一个一维的“动态”数组,当单元格被选中/取消选中时,它会明显地增大/缩小。从技术上讲,这个集合是一个“栈”或先进先出( FIFO )…集合中的“第一个”单元格是选定的“最后一个”单元格,集合中的“最后一个”单元格是选定的“第一个”单元格。这显然使您能够知道单元格被选择的顺序。这是经过设计的,也是有意义的,但是对于这种情况,选择单元格的顺序并不重要。与OS副本相同,我们只关心由所选单元格创建的矩形的“左上角”单元格的位置。
因为单元格的顺序与用户选择单元格的顺序相同,所以我们需要找到矩形选择的“左上角”单元格(浅绿色单元格R1C1)。示例:使用下图,使用Ctrl键按顺序多选单元格: col2、col1、col4和col3。

选定的单元格数组将类似于…
Index cellValue
0 R3C3
1 R1C4
2 R3C1
3 R5C2在本例中,我们希望找到集合中编号“最低”的列和行。我们可以遍历集合,或者,由于集合是可枚举的,我们可以简单地根据列索引对集合进行排序,然后获取第一个items列索引。然后按行索引对其进行排序,并获取第一个项目的行索引。它可能看起来像…
int colIndex = copyCollection.Cast<DataGridViewCell>().OrderBy(c => c.ColumnIndex).First().ColumnIndex;
int rowIndex = copyCollection.Cast<DataGridViewCell>().OrderBy(c => c.RowIndex).First().RowIndex;这将为我们提供选择矩形(图片中的粉红色矩形)的左上角单元格(浅绿色单元格)。在本例中,第一列是第一列,第一行是第一行。假设用户单击了“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;
}
}
}我希望这是有意义的和有帮助的。?
https://stackoverflow.com/questions/62924064
复制相似问题