使用智能标记智能导入和放置数据

介绍

智能标记用于让 Aspose.Cells 知道在 Microsoft Excel 设计器电子表格中放置哪些信息。智能标记允许您创建仅包含特定信息和格式的模板。

设计师电子表格和智能标记

Designer 电子表格是标准的 Excel 文件,其中包含视觉格式、公式和智能标记。它们可以包含引用一个或多个数据源的智能标记,例如来自项目的信息和相关联系人的信息。智能标记被写入您需要信息的单元格中。

所有智能标记都以 &= 开头。 &=Party.FullName 是数据标记的示例。如果数据标记产生多个项目,例如,一个完整的行,则随后的行会自动向下移动以为新信息腾出空间。因此,小计和总计可以放在数据标记之后的行上,以根据插入的数据进行计算。要对插入的行进行计算,请使用动态公式.

智能标记包括数据源字段名称大多数信息的部分。特殊信息也可以与变量和变量数组一起传递。变量始终只填充一个单元格,而变量数组可能填充多个单元格。每个细胞只使用一个数据标记。未使用的智能标记将被删除。

智能标记也可能包含参数。参数允许您修改信息的布局方式。它们作为逗号分隔列表附加到括号中的智能标记的末尾。

智能标记选项

&=数据源.字段名 &=[数据源].[字段名]&=$变量名 &=$变量数组 &==动态公式 &=&=重复动态公式

参数

允许使用以下参数:

  • 没有添加 不要添加额外的行来适应数据。
  • 跳过:n - 为每行数据跳过 n 行。
  • 升序:n要么降序:n - 在智能标记中对数据进行排序。如果 n 为 1,则该列是排序器的第一个键。对数据源进行处理后对数据进行排序。例如:&=Table1.Field3(升序:1)。
  • 水平的 从左到右而不是从上到下写入数据。
  • 数字 如果可能,将文本转换为数字。
  • 转移 向下或向右移动,创建额外的行或列以适应数据。 shift 参数的工作方式与 Microsoft Excel 中的相同。例如在Microsoft Excel中,当您选择一个单元格区域时,右键单击并选择插入并指定向下移动单元格, 右移单元格和其他选项。简而言之,转移参数为垂直/正常(从上到下)或水平(从左到右)智能标记填充相同的功能。
  • 文案风格 将基本单元格的样式复制到该列中的所有单元格。

可以组合参数 noadd 和 skip 以在交替行上插入数据。因为模板是从下往上处理的,所以你应该在第一行添加noadd以避免在备用行之前插入额外的行。

如果您有多个参数,请用逗号分隔它们,但不要使用空格:parameterA,parameterB,parameterC

以下屏幕截图显示了如何每隔一行插入数据。

模板文件 输出文件
待办事项:图片_替代_文本 待办事项:图片_替代_文本

动态公式

动态公式允许您将 Excel 公式插入到单元格中,即使公式引用了将在导出过程中插入的行。动态公式可以为每个插入的行重复或仅使用放置数据标记的单元格。

动态公式允许以下附加选项:

  • r - 当前行号。
  • 2, -1 - 当前行号的偏移量。

例如:

 &=&=B{-1}/C{-1}~(skip:1)

在动态公式标记中,“-1”分别表示B列和C列到当前行的偏移量,将设置除法运算,skip参数为一行。此外,我们应该指定以下字符:

 "~"

作为分隔符以在动态公式中应用更多参数。

以下屏幕截图说明了一个重复的动态公式和生成的 Excel 工作表。

模板文件 输出文件
待办事项:图片_替代_文本 待办事项:图片_替代_文本
Cell “C1”包含公式A1*B1 ,单元格“C2”包含A2*B2单元格“C3”包含A3*B3.

处理智能标记非常容易。下面是两个代码片段,一个在 C# 中,一个在 VB 中,展示了它是如何完成的。

// For complete examples and data files, please go to https://github.com/aspose-cells/Aspose.Cells-for-.NET
// The path to the documents directory.
string dataDir = RunExamples.GetDataDir(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
// Create directory if it is not already present.
bool IsExists = System.IO.Directory.Exists(dataDir);
if (!IsExists)
System.IO.Directory.CreateDirectory(dataDir);
//set the file path of designer spreadsheet containing smart markers
string designerFile = "templateDynamicFormulas.xlsx";
//create your data set
DataSet dataset = new DataSet();
DataTable dt = new DataTable("Answer");
dataset.Tables.Add(dt);
DataColumn price = new DataColumn("Price", typeof(double));
DataColumn amount = new DataColumn("Amount", typeof(int));
dt.Columns.Add(price);
dt.Columns.Add(amount);
dt.Rows.Add(100.00, 2);
dt.Rows.Add(75.25, 3);
dt.Rows.Add(25.00, 5);
if (designerFile != null)
{
// Instantiating a WorkbookDesigner object
WorkbookDesigner designer = new WorkbookDesigner();
// Open a designer spreadsheet containing smart markers
designer.Workbook = new Workbook(designerFile);
// Set the data source for the designer spreadsheet
designer.SetDataSource(dataset);
// Process the smart markers
designer.Process();
}

使用变量数组

以下示例代码显示了如何在智能标记中使用变量数组。我们将一个可变数组标记动态地放入工作簿的第一个工作表的 A1 单元格中,其中包含我们为标记设置的字符串值,处理标记以将数据填充到针对标记的单元格中。最后我们保存 Excel 文件。

// For complete examples and data files, please go to https://github.com/aspose-cells/Aspose.Cells-for-.NET
// The path to the documents directory.
string dataDir = RunExamples.GetDataDir(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
// Instantiate a new Workbook designer.
WorkbookDesigner report = new WorkbookDesigner();
// Get the first worksheet of the workbook.
Worksheet w = report.Workbook.Worksheets[0];
// Set the Variable Array marker to a cell.
// You may also place this Smart Marker into a template file manually in Ms Excel and then open this file via Workbook.
w.Cells["A1"].PutValue("&=$VariableArray");
// Set the DataSource for the marker(s).
report.SetDataSource("VariableArray", new string[] { "English", "Arabic", "Hindi", "Urdu", "French" });
// Process the markers.
report.Process(false);
// Save the Excel file.
report.Workbook.Save(dataDir + "output.xlsx");

分组数据

在某些 Excel 报告中,您可能需要将数据分组以使其更易于阅读和分析。将数据分组的主要目的之一是对每组记录运行计算(执行汇总操作)。

Aspose.Cells 智能标记允许您按字段对数据进行分组,并在数据集或数据组之间放置摘要行。例如,如果按 Customers.CustomerID 对数据进行分组,您可以在每次组更改时添加一条摘要记录。

参数

以下是一些用于分组数据的智能标记参数。

组:正常/合并/重复

我们支持三种类型的组,您可以在其中进行选择。

  • 普通的 group by field(s) 值对于列中的相应记录不重复;相反,它们每个数据组打印一次。
  • 合并 与正常参数相同的行为,除了它按每个组集的字段合并组中的单元格。
  • 重复 对相应记录重复按字段分组的值。

例如:&=Customers.CustomerID(group:merge)

跳过

在每组之后跳过指定数量的行。

例如,&=Employees.EmployeeID(group:normal,skip:1)

小计N

对group by字段相关的指定字段数据进行汇总操作。 N 表示 1 到 11 之间的数字,指定在计算数据列表中的小计时使用的函数。 (1=AVERAGE、2=COUNT、3=COUNTA、4=MAX、5=MIN、…9=SUM 等)有关详细信息,请参阅 Microsoft Excel 帮助中的小计参考。

格式实际上声明为: subtotalN:Ref 其中 Ref 指的是按列分组。

例如,

  • &=Products.Units(subtotal9:Products.ProductID) 指定汇总函数单位领域相对于产品编号中的字段产品桌子。
  • &=Tabx.Col3(subtotal9:Tabx.Col1) 指定汇总函数列3字段分组依据列1在表中制表符.
  • &=Table1.ColumnD(subtotal9:Table1.ColumnA&Table1.ColumnB) 指定汇总函数D列字段分组依据列AB列在表中表格1.

此示例显示了一些实际的分组参数。它使用 Northwind.mdb Microsoft Access 数据库并从名为“Order Details”的表中提取数据。我们在 Microsoft Excel 中创建一个名为 SmartMarker_Designer.xls 的设计器文件,并将智能标记放入工作表中的各个单元格中。处理标记以填充工作表。数据按组字段放置和组织。

设计器文件有两个工作表。首先,我们放置带有分组参数的智能标记,如下面的屏幕截图所示。放置了三个智能标记(带有分组参数): &=[订单详情].OrderID(group:merge,skip:1), &=[Order Details].Quantity(subtotal9:Order Details.OrderID), and &=[Order Details].UnitPrice(subtotal9:Order Details.OrderID)分别进入A5、B5、C5。

SmartMarker_Designer.xls 文件中的第一个工作表,包含智能标记
待办事项:图片_替代_文本
在设计器文件的第二个工作表中,我们放置了一些更智能的标记,如下图所示。我们放置以下智能标记:
&=[订单详情].OrderID(group:normal),
&=[订单详情].数量,
&=[订单详情].UnitPrice,
&=&=B(r)*C(r), 和
&=subtotal9:Order Details.OrderID分别为A5、B5、C5、D5、C6。
SmartMarker_Designer.xls 文件的第二个工作表,显示混合的智能标记。
待办事项:图片_替代_文本
这是示例中使用的源代码。
private void SmartMarkers_GroupingData()
{
//Examples-CSharp-SmartMarkers-GroupingData-1.cs
// For complete examples and data files, please go to https://github.com/aspose-cells/Aspose.Cells-for-.NET
// The path to the documents directory.
string dataDir = RunExamples.GetDataDir(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
// Create a connection object, specify the provider info and set the data source.
OleDbConnection con = new OleDbConnection("provider=microsoft.jet.oledb.4.0;data source=" + dataDir + "Northwind.mdb");
// Open the connection object.
con.Open();
// Create a command object and specify the SQL query.
OleDbCommand cmd = new OleDbCommand("Select * from [Order Details]", con);
// Create a data adapter object.
OleDbDataAdapter da = new OleDbDataAdapter();
// Specify the command.
da.SelectCommand = cmd;
// Create a dataset object.
DataSet ds = new DataSet();
// Fill the dataset with the table records.
da.Fill(ds, "Order Details");
// Create a datatable with respect to dataset table.
DataTable dt = ds.Tables["Order Details"];
// Create WorkbookDesigner object.
WorkbookDesigner wd = new WorkbookDesigner();
// Open the template file (which contains smart markers).
wd.Workbook = new Workbook(dataDir + "Designer.xlsx");
// Set the datatable as the data source.
wd.SetDataSource(dt);
// Process the smart markers to fill the data into the worksheets.
wd.Process(true);
// Save the excel file.
wd.Workbook.Save(dataDir + "output.xlsx");
}
class OleDbCommand
{
private string p;
private OleDbConnection con;
public OleDbCommand(string p, OleDbConnection con)
{
// TODO: Complete member initialization
this.p = p;
this.con = con;
}
}
class OleDbConnection
{
private string p;
public OleDbConnection(string p)
{
// TODO: Complete member initialization
this.p = p;
}
internal void Open()
{
}
}
class OleDbDataAdapter
{
public OleDbCommand SelectCommand { get; set; }
internal void Fill(System.Data.DataSet ds, string p)
{
}
}

使用匿名类型或自定义对象

Aspose.Cells 还支持智能标记中的匿名类型或自定义对象。下面的示例显示了它是如何工作的。要使用智能标记从动态对象导入数据,请访问以下文章:

从动态对象导入作为数据源

private void SmartMarkers_UsingAnonymousTypes()
{
// For complete examples and data files, please go to https://github.com/aspose-cells/Aspose.Cells-for-.NET
// The path to the documents directory.
string dataDir = RunExamples.GetDataDir(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
// Instantiate the workbookdesigner object.
WorkbookDesigner report = new WorkbookDesigner();
// Get the first worksheet(default sheet) in the workbook.
Aspose.Cells.Worksheet sheet = report.Workbook.Worksheets[0];
// Input some markers to the cells.
sheet.Cells["A1"].PutValue("Name");
sheet.Cells["B1"].PutValue("Age");
sheet.Cells["A2"].PutValue("&=MyProduct.Name");
sheet.Cells["B2"].PutValue("&=MyProduct.Age");
// Instantiate the list collection based on the custom class.
IList<Person> list = new List<Person>();
// Provide values for the markers using the custom class object.
list.Add(new Person("Simon", 30));
list.Add(new Person("Johnson", 33));
// Set the data source.
report.SetDataSource("MyProduct", list);
// Process the markers.
report.Process(false);
// Save the excel file.
report.Workbook.Save(dataDir + "Smart Marker Customobjects.xls");
}
// Definition of Custom class.
public class Person
{
private string m_Name;
public string Name
{
get { return m_Name; }
set { m_Name = value; }
}
private int m_Age;
public int Age
{
get { return m_Age; }
set { m_Age = value; }
}
internal Person(string name, int age)
{
this.m_Name = name;
this.m_Age = age;
}
}

图像标记

Aspose.Cells 智能标记也支持图像标记。本节介绍如何使用智能标记插入图片。

图像参数

用于管理图像的智能标记参数。

  • 图片:FitToCell - 使图像自动适合单元格的行高和列宽。
  • 图片:ScaleN - 将高度和宽度缩放到 N%。
  • 图片:宽:无&高:无 渲染图像 N 英寸高和 N 英寸宽。您还可以指定左侧和顶部位置(以磅为单位)。

这是示例中使用的源代码。

// For complete examples and data files, please go to https://github.com/aspose-cells/Aspose.Cells-for-.NET
// The path to the documents directory.
string dataDir = RunExamples.GetDataDir(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
// Get the image data.
byte[] imageData = File.ReadAllBytes(dataDir+ "aspose-logo.jpg");
// Create a datatable.
DataTable t = new DataTable("Table1");
// Add a column to save pictures.
DataColumn dc = t.Columns.Add("Picture");
// Set its data type.
dc.DataType = typeof(object);
// Add a new new record to it.
DataRow row = t.NewRow();
row[0] = imageData;
t.Rows.Add(row);
// Add another record (having picture) to it.
imageData = File.ReadAllBytes(dataDir+ "image2.jpg");
row = t.NewRow();
row[0] = imageData;
t.Rows.Add(row);
// Create WorkbookDesigner object.
WorkbookDesigner designer = new WorkbookDesigner();
// Open the template Excel file.
designer.Workbook = new Workbook(dataDir+ "TestSmartMarkers.xlsx");
// Set the datasource.
designer.SetDataSource(t);
// Process the markers.
designer.Process();
// Save the Excel file.
designer.Workbook.Save(dataDir+ "output.xls");

使用嵌套对象

Aspose.Cells 智能标记支持嵌套对象,嵌套对象要简单。我们使用一个简单的模板文件。请参阅包含一些嵌套智能标记的设计器电子表格。

SM_NestedObjects.xlsx 文件的第一个工作表显示了嵌套的智能标记。
待办事项:图片_替代_文本
下面的例子展示了它是如何工作的。
private void SmartMarkers_UsingNestedObjects()
{
// For complete examples and data files, please go to https://github.com/aspose-cells/Aspose.Cells-for-.NET
// The path to the documents directory.
string dataDir = RunExamples.GetDataDir(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
// ****** Program ******
// Initialize WorkbookDesigner object
WorkbookDesigner designer = new WorkbookDesigner();
// Load the template file
designer.Workbook = new Workbook(dataDir + "SM_NestedObjects.xlsx");
// Instantiate the List based on the class
System.Collections.Generic.ICollection<Individual> list = new System.Collections.Generic.List<Individual>();
// Create an object for the Individual class
Individual p1 = new Individual("Damian", 30);
// Create the relevant Wife class for the Individual
p1.Wife = new Wife("Dalya", 28);
// Create another object for the Individual class
Individual p2 = new Individual("Mack", 31);
// Create the relevant Wife class for the Individual
p2.Wife = new Wife("Maaria", 29);
// Add the objects to the list
list.Add(p1);
list.Add(p2);
// Specify the DataSource
designer.SetDataSource("Individual", list);
// Process the markers
designer.Process(false);
// Save the Excel file.
designer.Workbook.Save(dataDir + "output.xlsx");
}
class Individual
{
private String m_Name;
public String Name
{
get { return m_Name; }
set { m_Name = value; }
}
private int m_Age;
public int Age
{
get { return m_Age; }
set { m_Age = value; }
}
internal Individual(string name, int age)
{
this.Name = name;
this.Age = age;
}
private Wife m_Wife;
public Wife Wife
{
get { return m_Wife; }
set { m_Wife = value; }
}
}
public class Wife
{
public Wife(string name, int age)
{
this.m_name = name;
this.m_age = age;
}
private string m_name;
public string Name
{
get { return m_name; }
set { m_name = value; }
}
private int m_age;
public int Age
{
get { return m_age; }
set { m_age = value; }
}
}

使用通用列表作为嵌套对象

Aspose.Cells 现在也支持使用通用列表作为嵌套对象。请检查使用以下代码生成的输出 excel 文件的屏幕截图。正如您在屏幕截图中看到的,一个 Teacher 对象包含多个嵌套的 Student 对象。

待办事项:图片_替代_文本
// For complete examples and data files, please go to https://github.com/aspose-cells/Aspose.Cells-for-.NET
// The path to the documents directory.
string dataDir = RunExamples.GetDataDir(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
Workbook workbook = new Workbook();
// Create a designer workbook
// Workbook workbook = new Workbook();
Worksheet worksheet = workbook.Worksheets[0];
worksheet.Cells["A1"].PutValue("Teacher Name");
worksheet.Cells["A2"].PutValue("&=Teacher.Name");
worksheet.Cells["B1"].PutValue("Teacher Age");
worksheet.Cells["B2"].PutValue("&=Teacher.Age");
worksheet.Cells["C1"].PutValue("Student Name");
worksheet.Cells["C2"].PutValue("&=Teacher.Students.Name");
worksheet.Cells["D1"].PutValue("Student Age");
worksheet.Cells["D2"].PutValue("&=Teacher.Students.Age");
// Apply Style to A1:D1
Range range = worksheet.Cells.CreateRange("A1:D1");
Style style = workbook.CreateStyle();
style.Font.IsBold = true;
style.ForegroundColor = Color.Yellow;
style.Pattern = BackgroundType.Solid;
StyleFlag flag = new StyleFlag();
flag.All = true;
range.ApplyStyle(style, flag);
// Initialize WorkbookDesigner object
WorkbookDesigner designer = new WorkbookDesigner();
// Load the template file
designer.Workbook = workbook;
System.Collections.Generic.List<Teacher> list = new System.Collections.Generic.List<Teacher>();
// Create an object for the Teacher class
Teacher h1 = new Teacher("Mark John", 30);
// Create the relevant student objects for the Teacher object
h1.Students = new List<Person>();
h1.Students.Add(new Person("Chen Zhao", 14));
h1.Students.Add(new Person("Jamima Winfrey", 18));
h1.Students.Add(new Person("Reham Smith", 15));
// Create another object for the Teacher class
Teacher h2 = new Teacher("Masood Shankar", 40);
// Create the relevant student objects for the Teacher object
h2.Students = new List<Person>();
h2.Students.Add(new Person("Karishma Jathool", 16));
h2.Students.Add(new Person("Angela Rose", 13));
h2.Students.Add(new Person("Hina Khanna", 15));
// Add the objects to the list
list.Add(h1);
list.Add(h2);
// Specify the DataSource
designer.SetDataSource("Teacher", list);
// Process the markers
designer.Process();
// Autofit columns
worksheet.AutoFitColumns();
// Save the Excel file.
designer.Workbook.Save(dataDir + "output.xlsx");

使用智能标记的 HTML 属性

以下示例代码解释了智能标记的 HTML 属性的使用。当它被处理时,它会将“Hello World”中的“World”显示为粗体,因为 HTML标签。

// For complete examples and data files, please go to https://github.com/aspose-cells/Aspose.Cells-for-.NET
// The path to the documents directory.
string dataDir = RunExamples.GetDataDir(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
Workbook workbook = new Workbook();
WorkbookDesigner designer = new WorkbookDesigner();
designer.Workbook = workbook;
workbook.Worksheets[0].Cells["A1"].PutValue("&=$VariableArray(HTML)");
designer.SetDataSource("VariableArray", new String[] { "Hello <b>World</b>", "Arabic", "Hindi", "Urdu", "French" });
designer.Process();
workbook.Save(dataDir + "output.xls");

不是一行一行

目前默认的处理方式是逐行处理smartmaker。但是有时候同一个数据表的智能标记需要一起处理,不管 如果它们是否在同一行中,则必须在调用处理之前指定命名范围“_CellsSmartMarkers”并将 WorkbookDesigner.LineByLine 指定为 false。

|待办事项:图片_替代_文本|

string dataDir = RunExamples.GetDataDir(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
Workbook workbook = new Workbook();
Style style = workbook.CreateStyle();
style.Pattern = BackgroundType.Solid;
style.ForegroundColor = Color.Black;
style.Font.Color = Color.White;
// Create a designer workbook
// Workbook workbook = new Workbook();
Worksheet worksheet = workbook.Worksheets[0];
worksheet.Cells["A1"].PutValue("Teacher Name");
worksheet.Cells["A1"].SetStyle(style);
worksheet.Cells["A2"].PutValue("&=Teacher.Name");
worksheet.Cells["B1"].PutValue("Teacher Age");
worksheet.Cells["B1"].SetStyle(style);
worksheet.Cells["B2"].PutValue("&=Teacher.Age");
worksheet.Cells["A3"].PutValue("Student Name");
worksheet.Cells["A3"].SetStyle(style);
worksheet.Cells["A4"].PutValue("&=Teacher.Students.Name");
worksheet.Cells["B3"].PutValue("Student Age");
worksheet.Cells["B3"].SetStyle(style);
worksheet.Cells["B4"].PutValue("&=Teacher.Students.Age");
worksheet.AutoFitColumns();
//A named range "_CellsSmartMarkers" must be added for checking which range contains all smart markers about a table.
worksheet.Cells.CreateRange("A1:B4").Name = "_CellsSmartMarkers";
// Initialize WorkbookDesigner object
WorkbookDesigner designer = new WorkbookDesigner();
// Load the template file
designer.Workbook = workbook;
System.Collections.Generic.List<Teacher> list = new System.Collections.Generic.List<Teacher>();
// Create an object for the Teacher class
Teacher h1 = new Teacher("Mark John", 30);
// Create the relevant student objects for the Teacher object
h1.Students = new List<Person>();
h1.Students.Add(new Person("Chen Zhao", 14));
h1.Students.Add(new Person("Jamima Winfrey", 18));
h1.Students.Add(new Person("Reham Smith", 15));
// Create another object for the Teacher class
Teacher h2 = new Teacher("Masood Shankar", 40);
// Create the relevant student objects for the Teacher object
h2.Students = new List<Person>();
h2.Students.Add(new Person("Karishma Jathool", 16));
h2.Students.Add(new Person("Angela Rose", 13));
h2.Students.Add(new Person("Hina Khanna", 15));
// Add the objects to the list
list.Add(h1);
list.Add(h2);
// Specify the DataSource
designer.SetDataSource("Teacher", list);
designer.LineByLine = false;
// Process the markers
designer.Process();
// Autofit columns
worksheet.AutoFitColumns();
// Save the Excel file.
designer.Workbook.Save(dataDir + "output.xlsx");
public class Person
{
private string m_Name;
public string Name
{
get { return m_Name; }
set { m_Name = value; }
}
private int m_Age;
public int Age
{
get { return m_Age; }
set { m_Age = value; }
}
internal Person(string name, int age)
{
this.m_Name = name;
this.m_Age = age;
}
}
public class Teacher
{
private string m_Name;
public string Name
{
get { return m_Name; }
set { m_Name = value; }
}
private int m_Age;
public int Age
{
get { return m_Age; }
set { m_Age = value; }
}
private List<Person> mStudents;
public List<Person> Students
{
get { return mStudents; }
set { mStudents = value; }
}
public Teacher(string name, int age)
{
this.Name = name;
this.Age = age;
}
}

使用智能标记合并数据时获取通知

有时,可能需要在完成之前获得有关单元格引用或正在处理的特定智能标记的通知。这可以使用 WorkbookDesigner.CallBack 属性和 ISmartMarkerCallBack 来实现

推进主题