用于验证和导出数据的 Excel 加载项框架





5.00/5 (12投票s)
一个用于扫描工作表查找标题、读取和验证数据、向用户提供反馈并以表单形式显示结果数据的框架。
问题
用户通常需要从 Excel 加载信息:用户喜欢 Excel。然而,问题在于允许一定的灵活性:信息会在电子表格的哪个位置?如果用户重新排列列会怎样?
此外,还存在验证值并向用户反馈错误/问题的麻烦。您可以显示一个对话框,显示存在问题导致操作被取消,但那样用户就必须弄清楚在哪里修复问题。
可以像纳粹一样严格要求固定列,但这限制了以更易于使用的方式组织数据的能力。用户可能还使用电子表格来存储除要上传的信息之外的其他信息,因此最好不要要求标题行位于第一列的第一个单元格。也可能有一些列并非所有用户都有理由使用,如果用户删除了该列,也许应该允许他这样做。
概述
该程序被实现为一个 Excel 加载项项目。我试图为用户创建一个灵活的环境。我还强调了实现的灵活性,以便其他人能够轻松地获取代码。标题行几乎是可用性的必需品,否则用户将不知道将数据放在哪里,因此这是我确实对用户施加的一个限制。在我的情况下,我要求
- 每列具有特定的标题文本。
- 标题行是单行。
- 数据是标题行正下方的一组连续行。
- 用于确定标题行的搜索标题必须位于列/行中,其中列 + 行 < 100。
- 所有标题都必须在前 100 列内。
- 在包含标题文本的单元格之前,不能存在内容等于所搜索标题(列表中的第一个标题文本)的单元格。
- 要结束扫描,所有单元格都必须为空或无效。
这些要求可以轻松更改,但显然需要某种限制。
首先要求的是找到一个包含某列标题文本的单元格。为此,我创建了一个从左上角扫描单元格的方法,进行对角扫描。
public static bool ScanForText(Worksheet worksheet, object search,
out int columnIndex, out int rowIndex)
{
string searchText = search.ToString().ToLower();
for (int i = 1; i <= 100; i++)
{
for (rowIndex = 1, columnIndex = i; rowIndex <= i; rowIndex++, columnIndex--)
if (worksheet.Cells[rowIndex, columnIndex].Value != null &&
worksheet.Cells[rowIndex, columnIndex].Value.ToString().
ToLower() == searchText) return true;
}
columnIndex = -1; rowIndex = -1;
return false;
}
请注意,我为搜索设置了行和列的总和限制为小于 100,这在我看来是合理的,但可以轻松更改。调用此方法非常直接,只需检查此方法的返回标志即可确定搜索结果。如果找不到标题文本,我只会显示一个消息框然后返回。
工作表读取器
WorksheetReader(WorksheetReader
)类是一个通用类,负责查找标题行、读取和验证数据,然后将其传输到具体类的集合中,该类的属性与列标题相同。构造函数有一个 Excel 工作表的参数,它会保存该工作表,并设置标题对象(HeaderTextColumn
类)的集合,该集合将 Excel 行与列表中的标题文本关联起来。另一个参数是关于要读取的列的信息,包括标题、验证,其中包含ValueValidator
的枚举。此枚举用于生成HeaderTextColumn
集合,并且稍后为RowValidator
使用的字典提供验证器。
public WorksheetReader(Excel.Worksheet worksheet)
{
_worksheet = worksheet;
_headerColumns = valueValidators.Select
(i => new HeaderTextColumn(i.HeaderText, i.Required)).ToArray();
_valueValidators = valueValidators;
}
每个字段(“标题”)都有一个HeaderTextColumn
类的实例。由于它使用了生成ValueValidator
实例的相同集合,因此只需要在一个地方添加一个字段(当然,我们仍然需要一个类来实现实际保存数据的属性 {模型})。HeaderTextColumn
包含三个属性:
- 一个用于标题字符串的属性。
- 一个用于包含标题文本的列的属性。
- 一个用于指示是否需要标题继续处理的标志的属性。
找到包含标题的行后,下一个任务是为每个标题文本找到列。同样,我将搜索限制为 100,但在这种情况下是 100 列。在这种情况下,我使用WorksheetReader
类的一个关联方法来执行扫描,传递要搜索的行。该方法包含一个简单的搜索。
private bool ScanForHeaders(int rowIndex)
{
int counter = 0;
for (var columnIndex = 1; columnIndex < 100; columnIndex++)
{
if (_worksheet.Cells[rowIndex, columnIndex].Value != null)
{
Excel.Range cell = _worksheet.Cells[rowIndex, columnIndex];
string cellText = cell.Value.ToString().ToLower();
var headerColumn = _headerColumns.FirstOrDefault
(i => i.CellText.ToLower() == cellText);
if (headerColumn != null)
{
headerColumn.SetColumn(cell);
//No need to proceed further if found all titles
if (_headerColumns.Count() == ++counter) break;
}
}
}
return _headerColumns.All(i => !i.Required || i.ColumnIndex > 0);
}
您会注意到我检查了所有列是否都已找到,并在找到后停止搜索。此外,return
检查是否已找到所有必需的列。
返回值允许确定是否已将所有必需的标题与列相关联。为了帮助向用户显示错误,WorksheetReader
中还有另一个方法,该方法会格式化一个包含丢失标题列表的错误消息。
private string HeadersErrorMessage()
{
IEnumerable<string> names = _headerColumns.Where(
i => i.Required && i.ColumnIndex == 0).Select(i => i.CellText);
return string.Format(
"The following required headers are missing from the header row:{0} {1}.",
Environment.NewLine, string.Join(", ", names));
}
以下显示了在缺少必需标题时显示的错误消息。
现在我们有了包含标题的行以及每个标题的列,就可以开始读取每一行了。这也通过一个静态方法来完成。
private bool GetSingleItem(int rowIndex, out RowValidator validator)
{
validator = new RowValidator();
bool isNotEndingRow = false;
foreach (HeaderTextColumn headerColumn in _headerColumns)
{
int columnIndex = headerColumn.ColumnIndex;
if (columnIndex > 0)
isNotEndingRow |= headerColumn.UpdateFromCell(validator,
_worksheet.Cells[rowIndex, columnIndex]);
}
return isNotEndingRow;
}
返回值是RowValidator
类的一个实例或 null。当在与标题关联的任何列中未找到单个有效值时,将返回 null。因此,空白行将确保返回 null,这将停止行的加载。
这由GetItems
方法调用,该方法读取所有数据行,并在找到似乎不是数据行的行时停止进程。
private bool GetItems(int rowTitleIndex)
{
_validators = new List<RowValidator>();
do
{
RowValidator validator;
if (GetSingleItem(++rowTitleIndex, out validator))
_validators.Add(validator);
else
break;
} while (true);
// Mark cells with comments and highlight, or remove comments
foreach (var validator in Validators)
validator.MarkErrors();
return _validators.Any(i => i.IsValid);
}
它还负责遍历所有验证器,并在读取完成后调用MarkErrors
方法,如果未找到数据记录,则返回布尔值 false。
如果没有找到有效记录,则在此处停止处理并显示消息框。
HeaderColumn
类中有一个重要的方法,即UpdateFromCell
。UpdateFromCell
方法使用单元格的值更新作为参数传递的RowValidator
实例(与列标题关联的验证器)。
public bool UpdateFromCell(RowValidator record, Excel.Range cell)
{
try
{
record.SetValue(CellText, cell);
return null != cell.Value;
}
catch (ValidatorException)
{
return false;
}
}
RowValidator
实例具有SetValue
(和GetValue
)方法,该方法将更新与指定标题文本关联的值。此SetValue
方法在try/catch
块中使用,因为SetValue
方法负责验证值。如果值无效,RowValidator
将抛出ValidatorException
。这允许UpdateFromCell
返回一个指示值是否良好的值。
Validator 中的SetValue
调用ValueValidator
类的Validate
方法来保存值并进行验证;如果验证有问题,它将抛出ValidatorException
以指示该行具有无效值。
public void SetValue(string headerText, Excel.Range cell)
{
ValueValidator foundValidator = _validators[headerText];
foundValidator.Validate(cell);
}
WorksheetReader
的Start
方法被调用以启动读取,按顺序调用上述每个方法,检查操作是否成功,在出错时显示消息并中止,或者在操作成功时继续处理。
public void Start()
{
int rowTitleIndex;
int columnIndex;
string searchText = _headerColumns.Select(i => i.CellText).First();
if (!ExcelHelper.ScanForText(_worksheet, searchText, out columnIndex,
out rowTitleIndex))
MessageBox.Show(string.Format(
"Did not find a the header row.{0}(Searching for cell " +
"containing '{1}'){0}Aborting...",
Environment.NewLine, searchText), "Abort Reason",
MessageBoxButtons.OK, MessageBoxIcon.Error);
else if (!ScanForHeaders(rowTitleIndex))
MessageBox.Show(HeadersErrorMessage() + Environment.NewLine + "Aborting...",
"Abort Reason", MessageBoxButtons.OK, MessageBoxIcon.Error);
else if (!GetItems(rowTitleIndex))
MessageBox.Show(string.Format(
"Did not find any valid records during scan.{0}Aborting...",
Environment.NewLine), "Abort Reason", MessageBoxButtons.OK,
MessageBoxIcon.Error);
else if (typeof(T) == typeof(object)) //type of "object" will stop transfer.
return;
else if (!ReportResults())
return;
else
Transfer();
}
验证器
验证器用于验证和存储单个记录或行的值,该记录或行可以有许多值,每个值都与一个标题相关联。RowValidator
的核心是ValueValidator
集合。此列表作为参数在构造函数中提供,并用于生成用于保存和验证数据的Dictionary
。
public RowValidator(IEnumerable<ValueValidator> validators)
{
foreach (var validator in validators)
{
_validators.Add(validator.HeaderText, validator);
}
此集合的重要性不言而喻,因为它是与RowValidator
相关联的唯一变量。关键是要理解,这个集合不是一个简单的集合,因为每个RowValidator
必须为每行生成一个新的ValueValidator
实例。如果传递了一个简单的集合,那么每个RowValidator
将使用与特定列关联的每个ValueValidator
的同一个实例。传递的是一个生成器,每次使用yield return
执行迭代器时,它都会生成新的ValueValidator
实例。要向集合添加属性,迭代器中的一行代码可能如下所示:
yield return new ValueValidator("Desk", value =>
StaticValidators.Concatenate(
() => StaticValidators.IsNotNullOrEmpty(value),
() => StaticValidators.ToUpper(ref value),
() => StaticValidators.IsOneOf(value, "NBEM")),
true);
我认为使用yield return
比使用RowValidator
类中的ValueValidator
构造函数或其他地方更简洁。我很少使用yield return
,但这绝对是一个我认为yield return
的价值以新的方式闪耀的实例。
ValueValidator
的构造函数是:
public ValueValidator(string headerText, Func< apper, string> validator,
bool required)
{
_validator = validator;
HeaderText = headerText;
Required = required;
Validate(null, false);
}
请注意,validator 参数的类型是Func<StringWapper, string>
。StringWapper
只是将一个字符串包装在一个类中。这是因为字符串是不可变的,而我希望能够更新字符串。稍后我将介绍更新Value
的验证器示例。
public class StringWrapper
{
public string Value { get; set; }
public StringWrapper(string value) { Value = value; }
public static implicit operator string(StringWrapper value)
{
return value.Value;
}
public int Length { get { return Value.Length; } }
public override string ToString() { return Value; }
}
您会注意到我添加了一些额外的功能,这样在使用StringWrapper
类时就不必总是指定Value
属性。不幸的是,每当对Value
属性进行赋值时,都需要指定该属性。
验证方法包含在一个名为StaticValidators
的单独静态类中。上面命令中的Concatenate
方法是该类中关键方法之一。它用于在设置值时连接一系列测试。
public static string Concatenate(params Func<string>[] tests)
{
return tests.Select(test => test.Invoke()).
FirstOrDefault(result => result != null);
}
此函数允许运行任意数量的测试,遍历其参数,运行每个参数中指定的测试,如果测试返回 null,则继续下一个测试。测试方法的示例是:
public static string IsOneOf( apper value, params string[] values)
{
Contract.Assert(values != null && values.All(i => i != null),
"One of the values is null");
if (values.Any(i => i == value)) return null;
return String.Format("{{0}} must be one of the following values: {0}",
String.Join(", ", values));
}
您会注意到String.Format
调用中的“{{0}}”。目前,ValueValidator
的Validate
方法会将标题插入返回的错误消息中。
该应用程序中已创建了许多测试,并且添加更多测试非常容易。这些方法当前作为静态方法包含在StaticValidators
类中。
为了展示这种方法的灵活性,还可以包含一个替换方法。
public static string Substitute( apper value, string returnValue,
params string[] possibleValues)
{
var savedValue = value;
if (possibleValues.Any(i => i == savedValue))
{
value.Value = returnValue;
}
return null;
}
如果参数中存在匹配项,此方法将用另一个值替换该值。我还广泛使用一个强制将值转换为大写的方法。
public static string ToUpper(ref string value)
{
value.Value = value.Value == null ? null : value.Value.ToUpper();
return null;
}
Validator 中仅有的其他重要方法是GetValue
和SetValue
。实现很简单。我只是在两者中都添加了一个Contract.Assert
来捕获任何键不在字典中的问题。第三种方法只是遍历ValueValidator
实例的集合,并使用 LINQ 检查所有实例是否都有效。
public bool IsValid
{
get { return _validators.All(i => i.Value.IsValid); }
}
还有几个用于格式化单元格的方法,以向用户指示行值的问题,使用ValueValidator
实例来实际更改单元格格式。第二个方法清除格式更改。
为了将代码放在一起,我将生成ValueValidator
实例集合的代码放在了用于最终将数据从 Validator 实例保存到具有具体属性的表单的同一个类中。
值验证器
值验证器是一个类,用于包含有关单元格及其值的详细信息。它还包含实际启动验证的代码,并在发生错误时抛出包含错误消息的异常。
public void Validate(Excel.Range cell, bool throwException = true)
{
_errorMessage = null;
_cell = cell;
Value = cell == null ? string.Empty : cell.Value == null ?
string.Empty : cell.Value.ToString();
IsValid = true;
var validateReturn = new apper(Value);
_errorMessage = _validator(validateReturn);
Value = validateReturn.Value;
if (!string.IsNullOrEmpty(_errorMessage))
{
_errorMessage = string.Format(_errorMessage, HeaderText);
IsValid = false;
if (throwException)
throw new ValidatorException(_errorMessage);
}
}
正如您所看到的,此方法有很多职责:它必须保存值、验证值、保存单元格以及用于标记单元格的错误消息,以便用户可以看到值有问题,并抛出参数throwException
所需的异常。
与RowValidator
一样,还有几个用于格式化单元格的方法,以向用户指示行值的问题,使用ValueValidator
并清除格式。
格式化 Excel UI
目前实现的单元格包含一个标题,并给出黄色背景以指示找到的标题。如果数据行的单元格中存在错误,则该单元格将获得红色边框,并在单元格中添加一个带有错误消息的注释。
在完成工作表的读取之前,单元格不会被格式化以显示问题。ValueValidator
最终负责启动格式化。
public void MarkErrors()
{
if (!IsValid)
_savedCellFormating = ExcelHelper.AddErrorComment(_cell, _errorMessage);
}
AbstractVailidator
类中的MarkErrors
方法被ValueValidator
类中的MarkErrors
调用。
public void MarkErrors()
{
foreach (var validator in _validators)
validator.Value.MarkErrors();
}
这由WorksheetReader
类在读取记录完成后调用(这也使得使用BackgroundWorker
变得容易)。
ValueValidator
中还有一个RestoreFormatting
方法,它使用在MarkErrors
执行期间保存在类中的信息来将单元格属性恢复到MarkErrors
方法执行之前的格式。在WorksheetReader
类被销毁时,这些方法会在每个验证器上调用。
public void Dispose()
{
if (_validators != null)
{
var validators = _validators.ToArray();
for (int i = _validators.Count - 1; i > -1; i--)
validators[i].RestoreFormatting();
}
if (_headerColumns != null)
foreach (var headerColumn in _headerColumns)
headerColumn.RestoreFormatting ();
}
调用RestoreFormatting
必须按与MarkErrors
相反的顺序进行,以确保格式更改以相同的顺序撤销,因为可能存在共享边框。这就是为什么验证器保存在数组中并使用for
循环而不是foreach
循环的原因。析构函数负责反转行,验证器代码负责反转列。ValueValidator
实际调用执行恢复的辅助方法。显然,我们必须确保WorksheetReader
被销毁以确保恢复完成。在代码中,销毁会在初始化新WorksheetReader
实例之前调用。由于每次都创建一个新WorksheetReader
实例,在Dispose
方法中清理 Excel 工作表的格式,此清理效果很好。如果尝试使用同一个实例而不重置所有格式,将会大大增加处理连续单元格中错误的复杂性,这些单元格通过边框变色来识别。
从验证器传输数据
最后一步是根据需要将其传输到对象。该对象必须设计为具有与标题文本匹配的所有属性,以便静态ValidatorAdapter
类的Convert
方法能够有效工作。反射用于将数据从验证器传输到对象。
该适配器的核心在于构造函数。
public static List<T> Convert(IEnumerable<RowValidator> validators)
{
var results = new List<T>();
var properties = typeof(T).GetProperties();
foreach (var validator in validators.Where(i => i.IsValid))
{
var instance = new T();
foreach (var property in properties)
{
try
{
var value = validator.GetValue(property.Name);
object convertedValue = FixValue(property.PropertyType, value);
property.SetValue(instance, convertedValue, null);
}
catch (ArgumentException e)
{
// Will let issue pass since if important would
// have been found during development
DisplayValidatorIssue(e.Message);
}
}
results.Add(instance);
}
return results;
}
FixValue
方法使用TypeDescriptor.GetConverter
方法将值转换为正确的类型,如果存在转换器,否则代码可能会遇到问题,如果需要转换,可能会在构造函数中抛出异常。好处是反射SetValue
会将对象转换为正确的类型。
private static object FixValue(Type targetType, object value)
{
if (value.GetType() == targetType)
return value;
try
{
TypeConverter converter = TypeDescriptor.GetConverter(targetType);
return converter.ConvertFrom(value);
}
catch
{
DisplayConverterIssue(targetType, value);
return value;
}
}
关于TypeConverter
的说明:TypeConverter
在 WPF 中用于绑定过程中的转换。这意味着它非常擅长将字符串值转换为/自字符串值,能够转换颜色以及许多其他类型。
配置类
在此示例中,有一个名为Order
的单个类用于配置读取器。它有一个方法,该方法生成确保读取的值正确的验证器,以及与集合中每个项关联的一个简单属性,其名称与集合项中的标题文本值匹配。由于ValidatorClassAdapter
适配器的设计,属性可以是大多数类型,只要可以通过与类型关联的TypeConverter
将字符串值转换为其他类型。请注意,如果无法转换值,可能会发生异常,因此ValueValidator
中该属性的验证很重要,可以检查以确保字符串是正确的。
不一定需要将ValueValidator
生成器放在与属性相同的类中,但我喜欢这个解决方案,因为它将所有内容都放在一个类中。您可能更喜欢只创建一个用于属性的类,并在其他地方创建ValueValidator
生成器。
尽管如此,这种设计真的很不错,因为它很容易创建一个项目,该项目可以处理项目中的许多不同类型的传输,并且没有重复。所有核心代码都可以轻松地放在一个单独的项目中,并添加到库中。
Excel 加载项代码
代码的 Excel 部分从创建一个新的 Office 组下的 Excel 2010 加载项类型的项目开始。这会自动添加一个 Excel Host Item,其中包含一个名为ThisAddIn.cs的文件。可以重命名它,但似乎不值得(我认为它应该更容易)。我实际上不需要在ThisAddIn.cs文件中太多内容,并对其进行了简化,并包含了加载功能区的信息。
public partial class ThisAddIn
{
private void InternalStartup()
{
//Startup += ThisAddIn_Startup;
//Shutdown += ThisAddIn_Shutdown;
}
//private void ThisAddIn_Startup(object sender, System.EventArgs e)
//{
//}
//private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
//{
//}
protected override Microsoft.Office.Core.IRibbonExtensibility
CreateRibbonExtensibilityObject()
{
return new FoxExcelAddInRibbon();
}
}
接下来,我向项目添加了一个新的Ribbon
(XML)项,将其命名为FoxExcelAddInRibbon
(我不喜欢将名称保留为Ribbon1
)。这会在项目中添加一个 XML 文件和一个cs文件。如果打开FoxExcelAddInRibbon.cs文件,您将看到我添加到ThisAddIn.cs文件中的代码。在功能区代码中,您会看到很多代码,大部分是为我生成的,但我进行了一些清理。我唯一添加的代码是用于处理功能区按钮的事件处理程序,以及用于获取定义为资源的自定义图像的一些代码。我将此处的代码保持相当简单,可能应该使其更简单。在功能区 XML 中,我定义了三个按钮。
<?xml version="1.0" encoding="UTF-8"?>
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui"
onLoad="Ribbon_Load">
<ribbon>
<tabs>
<tab idMso="TabAddIns">
<group id="FxOrders"
label="FX Orders">
<button id="ButtonExecuteOrders"
size="large"
label="Read Orders"
onAction="OnExecuteOrders"
getImage="GetCustomImage"
tag="Search"/>
<button id="ButtonInsertTitleRow"
size="large"
label="Insert Titles"
onAction="OnInsertTitleRow"
imageMso="CreateTableTemplatesGallery"/>
<button id="ButtonShowResults"
size="large"
label="Show Results"
onAction="OnShowResults"
imageMso="GroupListToolQuickView"
screentip=" Show results in new window"/>
</group>
</tab>
</tabs>
</ribbon>
</customUI>
确保在第一个“<”和文件开头之间没有空格,因为软件对这里的任何空格都很挑剔。我使用了一个自定义图标按钮(第一个),其中getImage
属性中是我在代码隐藏中的过程名称,并且tag
属性中传递了资源的名称。另外两个按钮使用 Office2010IconsGallery Word 文件中预定义的图像,该文件可下载为http://www.microsoft.com/en-us/download/details.aspx?id=21103。
显示结果
如果别的没有,在这种测试示例中有一个显示结果的方法是很好的。然而,在实际应用程序中,它会给用户更多信心,让他们相信他们的数据将被上传。由于我可以使用 Excel 显示这些结果,因此这个窗体并非绝对必需,但项目工程师想要这个功能。
我为此功能使用了一个 Windows 窗体。可以使用 WPF(我通常用 WPF 编程),但这很麻烦,所以 Windows 窗体是最佳选择。我创建的窗体围绕在DataGridView
控件中显示数据。我使用停靠功能允许用户调整视图大小。窗体底部还有一个面板,其中包含取消和提交按钮、一个Label
用于显示转换结果的概述,以及一个简单的图标,如果所有行都通过验证,则显示绿色,否则显示黄色。
最终结果显示不成问题,因为DataGridView
可以轻松绑定到具有要显示数据属性的对象集合。然而,设计是这样的:只有有效行才会被转换为对象;否则,只有RowValidation
对象集合中的数据,其中数据实际上存储在字典中。我最初尝试使用DynamicObject
适配器来显示数据,但它无法识别DynamicObject
中的属性。我使用DataTable
进行了编码,但这可行,但必须将字典条目转换为行中的字段。然后有人指出ICustomTypeDescriptor
接口和PropertyDescriptor
类。这很容易定制,以便与Dictionary<string, ValueValidator>
数据源接口。
所有ICustomTypeDescriptor
所做的就是提供一个存储字典的地方,并返回一个PropertyDescriptorCollection
,其中包含每个属性的PropertyDescriptor
实例,使用GetProperties
方法。
我创建了一个继承自PropertyDescriptor
的RowValidatorDescriptor
,为ValueValidator
实例提供适配器。在此实现中有两个重要方法:PropertyType
(返回类型)和GetValue
。在这种情况下,类型始终是string
,GetValue
必须搜索正确的ValueValidator
并返回其Value
属性。
我对这个类做了一个小小的定制,以便在每行旁边显示一个图标。ICustomTypeDescriptor
类包含一个额外的PropertyDescriptor
实例用于此图标字段,并且该字段的PropertyDescriptor
返回一个图像,该图像取决于所有ValueValidator
实例的IsValid
属性是否为 true。唯一棘手的部分是ICustomTypeDescriptor
的GetProperties
方法中的 LINQ 语句。
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return new PropertyDescriptorCollection(
(new[] { new RowIsValidValidatorDescriptor() }).
Cast<PropertyDescriptor>().Union(
_valueValidators.Keys.Select(
key => new RowValudValidatorDescriptor(key))).ToArray());
}
LINQ 需要Cast
来确保这两种不同类型的PropertyDescriptor
实例能够正常工作。
自定义示例
支持自定义的所有文件都在ExporterCustom文件夹中。您会注意到该文件夹中有几个额外的文件。OrderAdapter.cs是用于传输信息并包含验证信息的类。CustomValidators.cs包含一个特殊验证器,用于检查经纪人列表是否是三个字符的单词列表。StringList.cs在OrderAdapter
中用作经纪人的类型。它派生自IEnumerable<string>
。我将此类用于经纪人的原因是,我可以定义另一个类StringListTypeConverter.cs,它将在ValidatorAdapter
中将字符串转换为StringList
类型。TypeConverter
是必需的,因为ValidatorAdapter
使用类的TypeConverter
将值转换为正确的类型。这两个类允许我自动将字符串转换为列表,而无需额外的代码。
Excel 问题
我在恢复格式方面有一个重大问题。如果用户更改了格式,它会工作得很好,但当我尝试恢复边框时,我会得到一个黑色边框。我不知道如何解决这个问题。
Excel 加载项说明
我经常发现加载项在 Excel 中未运行。这几乎肯定是因为它已被禁用。在 Excel 2010 中,您需要转到“文件”选项卡,单击“选项”按钮,选择“加载项”选项卡/按钮,然后在“管理”组合框中选择“已禁用项”。然后您必须单击“转到...”按钮。在对话框中,单击列表框中加载项的名称,然后单击“启用”按钮。不幸的是,Visual Studio 和 Excel 都不会通知您加载项已被禁用。这很方便。如果我知道这个问题,它本来可以为我节省一些时间。
结论
如果您经常与喜欢使用 Excel 输入数据的客户打交道,这是一个可以添加到您的工具包中的绝佳工具,它能让您快速创建一个新的 Excel 导出,该导出将提供验证并向用户反馈他们数据中的问题。现在可以通过提供更多关于传输的信息来进一步改进它,我相信这段代码将在我的团队中不断发展。