6

在我开始解决问题之前,我想激发它。我的任务是分析 Excel 表中的更改,但与通过内置机制记录更改不同,应以编程方式检测更改,即使用户停用更改记录或未安装我的 Excel-AddIn。因此,我使用 Microsoft.Interop.Excel-Library 来访问工作表和其中的单元格。

现在来解决问题:为了查找更改,即使用户已经对数据进行了排序或移动,我也希望每个单元格都有一个唯一的 id,即使移动或复制,它也会坚持下去。当然,如果复制,id 在工作表中是两次,新添加的单元格没有 id,但这没关系。此外,此 ID 不应该对用户可见,并且用户不应该能够修改或删除它。

所以我找了一个字段,发现Range-Object可以代表一个单元格,并且有不同的可以访问的成员。一个特殊的字段引起了我的注意,ID 字段,看起来就像我正在搜索的内容。

Guid guid = Guid.NewGuid();
((Range) worksheet.Cells[rowNr, columnNr]).ID = guid.ToString();

也可以像阅读

Guid guid = Guid.Parse(((Range) worksheet.Cells[rowNr, columnNr]).ID);

这是完美的,因为我能够存储一个字符串(在本例中是一个 Guid 作为字符串,如 123463-fc34-c43a-a391-399fc2)并读取它。它也粘在单元格上,并在单元格移动时被移动等。

但不幸的是,当文件保存时,这个 ID 字段没有持久化,我不知道为什么。我的意思是,在关闭并重新打开工作簿后,所有 ID 都消失了。

所以我的问题是,如果 Range 对象有任何其他成员可以保存一个字符串 (=Guid),并且对用户不可见。我尝试了 Name-Member 和 Comment-Member,但它们对用户都是可见的,并且可以轻松修改。

或者有没有办法告诉 Excel 在保存工作表时我也想保存 ID 字段?

为了进行测试,您可以创建一个项目,添加对 Microsoft.Office.Interop.Excel-Dll 的引用并添加以下代码(您的系统上必须安装 Excel)。这是一个单元测试,与 JUnit 一起运行,但只需删除 Assert-Command 即可在没有 JUnit 的情况下对其进行测试:

using System;
using System.IO;
using Microsoft.Office.Interop.Excel;
using Excel = Microsoft.Office.Interop.Excel;
public void AddGuidAndRead() 
{
    Excel.Application excelApp = new Excel.Application();
    Workbook excelWorkbook = excelApp.Workbooks.Add(Type.Missing);
    Worksheet worksheet = excelWorkbook.Sheets[1]; //1-based index

    Guid rowGuid1 = Guid.NewGuid();
    const string filename = "C:\\temp\\anyTemporaryFilename.xlsx";

    //Make sure, this file does not exist previously
    if (File.Exists(filename))
        File.Delete(filename);

    //Write the ID to the worksheet
    ((Range)worksheet.Cells[1, 1]).ID = rowGuid1.ToString();

    //Act (save and close the workbook)
    excelWorkbook.SaveAs(filename);
    excelWorkbook.Close();

    //Now open the workbook again
    Workbook openedWorkbook = excelApp.Workbooks.Open(filename);
    //Fetch the worksheet, where we worked previously
    Worksheet openedWorksheet = openedWorkbook.Sheets[1]; //1-based index

    //Read the ID from the cell
    string guid1 = ((Range)openedWorksheet.Cells[1, 1]).ID;

    //Cleanup
    openedWorkbook.Close(false);
    File.Delete(filename);
    excelWorkbook.Close(false, Type.Missing, Type.Missing);
    excelApp.Quit();

    //Assert - this fails!!
    Assert.AreEqual(rowGuid1.ToString(), guid1);

}

我会很感激任何想法,在保存工作表或有关此主题的任何内容时,如何将 ID 放入持久的 Excel-Worksheet-Cell 中。

非常感谢,亚历克斯

2011 年 5 月 14 日更新:

由于以下原因,名称字段似乎不能解决我的问题:

首先,也是最严重的事实是,名称似乎必须是唯一的,但我想为一行中的所有单元格赋予相同的 ID,但这是行不通的。

其次,在 C# 中访问 Name-Field 对我来说并不是很清楚。您可以使用

((Range)worksheet.Cells[rowNr, columnNr]).Name = guid.ToString();
//Remark: Special dealing with guids required, 
//if they start with a number or contain special characters.

但它有严重的问题。如果名称已设置,则抛出异常,如果未设置名称,则尝试使用

string name = ((Range)worksheet.Cells[rowNr, columnNr]).Name.Name;

你得到一个例外。您需要 Name.Name,因为第一个 Name-field 不是字符串,而是一个完整的 Name-Object,其中包含另一个 Name-Field,其中包含一个字符串。

最后,如果您想检查它是否有名称,则不能执行以下操作:

if(((Range)worksheet.Cells[rowNr, columnNr]).Name == null)
    //Do something

因为它在访问不存在的名称字段时已经抛出异常。

4

3 回答 3

2

实际上,没有办法像我想的那样直接将 ID 保留在工作表中。既不在 ID 字段中(不持久),也不作为名称(仅允许唯一名称)或评论(对用户可见)。

但是工作簿中有 CustomProperties 的概念,它可以保存字符串,并且由于所有可序列化的类都可以编组为字符串,这允许程序员单独保存 ID 并在加载工作簿时恢复它们。

无论如何,为了我的目的,使用了另一种方法,即计算每行的哈希值并比较行哈希值。

于 2011-07-13T06:52:15.183 回答
1

您可以尝试为每个单元格使用命名范围。该名称将持续存在。我没有尝试过使用互操作,但它适用于好的旧 vba。在下面的代码中,请注意名称可以对用户隐藏。

Function try()
    Dim x As Integer
    Dim y As String

    Worksheets("Sheet1").Range("a1").Name = "_firstCell"
    Range("_firstCell").Value = 9999
    Dim nm As Name
    'hide
     For Each nm In ActiveWorkbook.Names
        If Left(nm.Name, 1) = "_" Then
            nm.Visible = False
        End If
    Next
    'move the named cell
    Range("_firstCell").Cut Range("b1")
    'check the value and address
    x = Range("_firstCell").Value
    y = Range("_firstCell").Address

End Function 

据我了解,工作簿中命名范围的数量没有逻辑限制。

于 2011-05-13T21:09:03.720 回答
0

尝试使用:FormatConditions activecell.FormatConditions.Add xlExpression, formula1:="test_1234" 以获取 ID IDRange = mid(activecell.FormatConditions(1).formula1,2) "test_1234" 的值

于 2016-02-15T04:49:14.163 回答