前言

  相信很多做WPF开发的小伙伴都遇到过表格类的需求,虽然现有的Grid控件也能实现,但是使用起来的体验感并不好,比如要实现一个Excel中的表格效果,估计你能想到的第一个方法就是套Border控件,用这种方法你需要控制每个Border的边框,并且在一堆Bordr中找到Grid.Row,Grid.Column来确定位置,明明很简单的一个功能,硬是耗费了大量时间。Grid的这种设计虽然功能很强大,但是同时也导致了操作繁琐可读性非常差的问题。此时做过web开发的人肯定很想念html中的table元素,没错,我也是这样想的,如果能把html中的table元素搬到WPF中,那问题就轻松解决了,今天我们就来解决这个问题。

 

一、准备工作

我们先来认识一下table元素,其实最开始的网页功能相对简单,table元素主要用于展示文本和基本的排版。然而随着html标准的更新,table元素越来越复杂,很多功能在不同的标准中写法可能不一样,甚至有的功能只能在css中实现,这种情况我们成全照搬html中的写法肯定不现实,也完全没必要。所以必须做一个取舍。由于WPF中并没有css的概念,所以我们尽量舍弃css中的写法,使用WPF中类似的属性写法来开发,以下为统计出来的可用属性。

 

二、需求分析

既然我们要复刻一个东西,第一步肯定是要先搞清楚这个东西的内在逻辑,所以我们先来看看html中的table元素是怎么回事。

2.1 table结构

<table>
<tr>
<th>header1</th>
<th>header2</th>
<th>header3</th>
</tr>
<tr>
<td>value1</td>
<td>value2</td>
<td>value3</td>
</tr>
<tr>
<td>value4</td>
<td>value5</td>
<td>value6</td>
</tr>
</table>

 2.1.1 table

table为表格根元素,table内可以放置多个tr。

    2.1.2 tr 

tr表示表格中的一行,一行可以放置若干个td。

    2.1.3 td

td为表格单元格,td可以设置rowspan属性合并多个行,可以设置colspan合并多个列。

2.2 尺寸单位

    2.2.1适用范围

table的width,height属性,tr的height属性,td的width,heigth属性。

    2.2.2 取值范围

    1. 百度比(例:width="50%")

    2. 像素(例:width="500")

    3. 不设置(自动计算)

2.3 布局逻辑

    2.3.1 table

   2.3.1.1 width="50%"

宽度占可用空间的50%,当父控件尺寸改变时会重新计算宽度,如果所有td子元素的尺寸之合大于table宽度(width="50%"),table宽度==Sum(td.width)。

2.3.1.2 width="500"

宽度占500像素,当父控件尺寸改变时不会重新计算宽度,如果所有td子元素的尺寸之合大于table宽度(width="500"),table宽度==Sum(td.width)。

2.3.1.3 不设置宽度

不设置宽度的情况下,宽度根据td子元素的宽度计算,Sum(td.width)。

    2.3.2 tr

    2.3.2.1 height="50%"

高度占table元素总高的50%,当父控件尺寸改变时会重新计算高度,当tr中高度最高的td超过了tr的50%时,整行高度以该td的高度为准。

  2.3.2.2 height="500"

高度占500像素,当父控件尺寸改变时会重新计算高度,当tr中高度最高的td超过了tr的500像素时,整行高度以该td的高度为准。

        2.3.2.3 不设置高度

不设置高度的情况下,以最高的td子元素为准。

    2.3.3 td

2.3.3.1 width="50%"

宽度占table宽度的50%,当剩余宽度不足以分配给其它列时会压缩该列的50%宽度,分配给其它列。该列的实际宽度以该列所有td的最大宽度为准。

 2.3.3.2 width="50"

宽度占50像素,当剩余宽度不足以分配给其它列时会压缩该列的50像素宽度,分配给其它列。该列的实际宽度以该列所有td的最大宽度为准。

        2.3.3.3 不设置宽度

不设置宽度的情况下,如果其它设置了宽度的列分配完宽度后,剩余宽度大于所有td的最小宽度的总合,那么未设置宽度的列会平均分配剩余的宽度,如果剩余的宽度小于所有td最小宽度的总合,那么所有td的宽度按最小宽度分配,其它已设置宽度的列则压缩宽度。该列的实际宽度以该列所有td的最大宽度为准。

        2.3.3.4 height="50%"

高度占table高度的50%,当剩余高度不足以分配给其它行时会压缩该行的50%高度,分配给其它行。该行的实际高度以该行所有td的最大高度为准。如果最高td的高度大于tr,则以最高的td为准,如果小于tr,则以tr的高度为准。

        2.3.3.5 height="50"

高度占50像素,当剩余高度不足以分配给其它行时会压缩该行的50像素高度,分配给其它行。该行的实际高度以该行所有td的最大高度为准。如果最高td的高度大于tr,则以最高的td为准,如果小于tr,则以tr的高度为准。

        2.3.3.6 不设置高度

不设置高度的情况下,如果其它设置了高度的行分配完高度后,剩余高度大于所有td的最小高度的总合,那么未设置高度的行会平均分配剩余的高度,如果剩余的高度小于所有td最小高度的总合,那么所有td的高度按最小高度分配,其它已设置高度的行则压缩高度。该行的实际高度以该列所有td的最大高度为准。

三、能实现

  通过对需求的分析,我们知道至少应该有3个类来实现表格功能,分别是Table、Tr、Td,我们下面来看看怎么来实现它们。

3.1 Table控件

  Table是一个在界面上需要呈现的元素,该控件主要处理布局及排列,不需要控件模板,所以不应该继承自Control类,那么可不可以继承自Panel呢,明显也不行,Panel的尺寸及布局系统继承自FrameworkElement,并不能给它的宽度设置Width="50%"这种值,所以它不仅不能继承自Panel,也不能继承自FrameworkElement,所以Table应该继承自UIElement类,我们需要在Table写自己的尺寸及布局管理功能,以下为Talbe的示例代码。

[ContentProperty("Rows")]
public class Table : UIElement
{
/// <summary>
/// 获取或设置行
/// </summary>
public TrCollection Rows
{
get { return (TrCollection)GetValue(RowsProperty); }
private set { SetValue(RowsProperty, value); }
} public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register("Rows", typeof(TrCollection), typeof(Table)); /// <summary>
/// 获取或设置宽度
/// </summary>
public TableLength Width
{
get { return (TableLength)GetValue(WidthProperty); }
set { SetValue(WidthProperty, value); }
} public static readonly DependencyProperty WidthProperty =
DependencyProperty.Register("Width", typeof(TableLength), typeof(Table)); /// <summary>
/// 获取或设置高度
/// </summary>
public TableLength Height
{
get { return (TableLength)GetValue(HeightProperty); }
set { SetValue(HeightProperty, value); }
} public static readonly DependencyProperty HeightProperty =
DependencyProperty.Register("Height", typeof(TableLength), typeof(Table));
}

3.2 Tr

Tr在Table里主要的作用是表达逻辑关系,不需要在界面上呈现,所以我们可以让它继承自DependencyObject,可以绑定属性就行了,以下为示例代码。

[ContentProperty("Cells")]
public class Tr : DependencyObject
{
/// <summary>
/// 获取或设置单元格
/// </summary>
public TdCollection Cells
{
get { return (TdCollection)GetValue(CellsProperty); }
private set { SetValue(CellsProperty, value); }
} public static readonly DependencyProperty CellsProperty =
DependencyProperty.Register("Cells", typeof(TdCollection), typeof(Tr)); /// <summary>
/// 获取或设置高度
/// </summary>
public TableLength Height
{
get { return (TableLength)GetValue(HeightProperty); }
set { SetValue(HeightProperty, value); }
} public static readonly DependencyProperty HeightProperty =
DependencyProperty.Register("Height", typeof(TableLength), typeof(Tr));
}

3.3 Td

Td的情况与Table类似,需要在界面上呈现,并且有自己的尺寸及布局逻辑,所以继承自UIElement,以下为示例代码。

public class Td : UIElement
{
/// <summary>
/// 获取或设置需要跨的列数
/// </summary>
public int ColSpan
{
get { return (int)GetValue(ColSpanProperty); }
set { SetValue(ColSpanProperty, value); }
} public static readonly DependencyProperty ColSpanProperty =
DependencyProperty.Register("ColSpan", typeof(int), typeof(Td), new PropertyMetadata(1)); /// <summary>
/// 获取或设置需要跨的行数
/// </summary>
public int RowSpan
{
get { return (int)GetValue(RowSpanProperty); }
set { SetValue(RowSpanProperty, value); }
} public static readonly DependencyProperty RowSpanProperty =
DependencyProperty.Register("RowSpan", typeof(int), typeof(Td), new PropertyMetadata(1)); /// <summary>
/// 获取或设置宽度
/// </summary>
public TableLength Width
{
get { return (TableLength)GetValue(WidthProperty); }
set { SetValue(WidthProperty, value); }
} public static readonly DependencyProperty WidthProperty =
DependencyProperty.Register("Width", typeof(TableLength), typeof(Table)); /// <summary>
/// 获取或设置高度
/// </summary>
public TableLength Height
{
get { return (TableLength)GetValue(HeightProperty); }
set { SetValue(HeightProperty, value); }
} public static readonly DependencyProperty HeightProperty =
DependencyProperty.Register("Height", typeof(TableLength), typeof(Table));
}

 

3.4.1 MeasureCore()

该方法传入一个名为availableSize的Size参数,该参数为控件可用的最大尺寸,我们需要通过这个参数计算各个单元格的排列位置及尺寸,并根据排列情况返回一个控件的期望尺寸,以下为实现的大致流程。

1 通过Table的Height及Width参数计算出真实的尺寸;

2 读取Table的Tr属性,取出所有Td,并定义一个二维数组将所有Td存放进去(Td[n,n]),如果Td的RowSpan或ColSpan参数大于1则将被合并的位置存入一个null。

3 根据第2步的填充结果再定义一个存放坐标的二维数据(Size[n,n]);

4 测量Td子元素的尺寸,计算每个单元格实际尺寸,根据Td子元素尺寸计算是否需要压缩尺寸,计算完成后将单元格的尺寸存入第3步的数组中;

5 根据第3步保存的尺寸数据计算单元格跨行或跨列后的尺寸;

6 将计算出的Table实际尺寸返回给MeasureCore方法,以供下一步排列使用;

    3.4.2 ArrangeCore()

该方法处理子控件的位置排列,循环调用每一个单元格的Arrange()方法,传入测量位置及尺寸就可以了。

    3.4.3 OnRender()

该方法读取BorderColor、BgColor等参数画线及填充颜色,表格的外观都是由它画出来的。

四、行效果

4.1 默认效果

<qs:Table
Width="50%"
Height="50%"
Align="Center"
Border="1 solid red"
Valign="Middle">
<qs:Tr>
<qs:Th>11</qs:Th>
<qs:Th>12</qs:Th>
<qs:Th>13</qs:Th>
<qs:Th>14</qs:Th>
<qs:Th>15</qs:Th>
<qs:Th>16</qs:Th>
</qs:Tr>
<qs:Tr>
<qs:Td>21</qs:Td>
<qs:Td>22</qs:Td>
<qs:Td>23</qs:Td>
<qs:Td>24</qs:Td>
<qs:Td>25</qs:Td>
<qs:Td>26</qs:Td>
</qs:Tr>
<qs:Tr>
<qs:Td>31</qs:Td>
<qs:Td>32</qs:Td>
<qs:Td>33</qs:Td>
<qs:Td>34</qs:Td>
<qs:Td>35</qs:Td>
<qs:Td>36</qs:Td>
</qs:Tr>
<qs:Tr>
<qs:Td>41</qs:Td>
<qs:Td>42</qs:Td>
<qs:Td>43</qs:Td>
<qs:Td>44</qs:Td>
<qs:Td>45</qs:Td>
<qs:Td>46</qs:Td>
</qs:Tr>
</qs:Table>

4.2 合并相邻的线

<qs:Table
Width="50%"
Height="50%"
Align="Center"
Border="1 solid red collapse"
Valign="Middle">
<qs:Tr>
<qs:Th>11</qs:Th>
<qs:Th>12</qs:Th>
<qs:Th>13</qs:Th>
<qs:Th>14</qs:Th>
<qs:Th>15</qs:Th>
<qs:Th>16</qs:Th>
</qs:Tr>
<qs:Tr>
<qs:Td>21</qs:Td>
<qs:Td>22</qs:Td>
<qs:Td>23</qs:Td>
<qs:Td>24</qs:Td>
<qs:Td>25</qs:Td>
<qs:Td>26</qs:Td>
</qs:Tr>
<qs:Tr>
<qs:Td>31</qs:Td>
<qs:Td>32</qs:Td>
<qs:Td>33</qs:Td>
<qs:Td>34</qs:Td>
<qs:Td>35</qs:Td>
<qs:Td>36</qs:Td>
</qs:Tr>
<qs:Tr>
<qs:Td>41</qs:Td>
<qs:Td>42</qs:Td>
<qs:Td>43</qs:Td>
<qs:Td>44</qs:Td>
<qs:Td>45</qs:Td>
<qs:Td>46</qs:Td>
</qs:Tr>
</qs:Table>

4.3 合并单元格

<qs:Table
Width="50%"
Height="50%"
Align="Center"
Border="1 solid red collapse"
Valign="Middle">
<qs:Tr>
<qs:Th>11</qs:Th>
<qs:Th>12</qs:Th>
<qs:Th>13</qs:Th>
<qs:Th>14</qs:Th>
<qs:Th>15</qs:Th>
<qs:Th>16</qs:Th>
</qs:Tr>
<qs:Tr>
<qs:Td ColSpan="6">21</qs:Td>
</qs:Tr>
<qs:Tr>
<qs:Td RowSpan="2">31</qs:Td>
<qs:Td>32</qs:Td>
<qs:Td ColSpan="2" RowSpan="2">33</qs:Td>
<qs:Td>35</qs:Td>
<qs:Td RowSpan="2">36</qs:Td>
</qs:Tr>
<qs:Tr>
<qs:Td>42</qs:Td>
<qs:Td>45</qs:Td>
</qs:Tr>
</qs:Table>

4.4 综合案例

<qs:Table
Width="50%"
Height="50%"
Align="Center"
Border="1 solid Black collapse"
Valign="Middle">
<qs:Tr
Height="40"
Align="Center"
BgColor="#FFAAAAAA"
Valign="Middle">
<qs:Th Width="40" BgColor="#FFAAAAAA">11</qs:Th>
<qs:Th Width="10%">12</qs:Th>
<qs:Th>13</qs:Th>
<qs:Th>14</qs:Th>
<qs:Th>15</qs:Th>
<qs:Th>16</qs:Th>
</qs:Tr>
<qs:Tr
Height="30%"
Align="Center"
BgColor="#FF5F5FF1"
Valign="Middle">
<qs:Td BgColor="#FFAAAAAA">21</qs:Td>
<qs:Td>22</qs:Td>
<qs:Td Width="20%">23</qs:Td>
<qs:Td>24</qs:Td>
<qs:Td>25</qs:Td>
<qs:Td>26</qs:Td>
</qs:Tr>
<qs:Tr
Height="30%"
Align="Center"
BgColor="#FFEA8633"
Valign="Middle">
<qs:Td BgColor="#FFAAAAAA">31</qs:Td>
<qs:Td>32</qs:Td>
<qs:Td>33</qs:Td>
<qs:Td>34</qs:Td>
<qs:Td>35</qs:Td>
<qs:Td>36</qs:Td>
</qs:Tr>
<qs:Tr
Height="30%"
Align="Center"
BgColor="#FF5F5FF1"
Valign="Middle">
<qs:Td BgColor="#FFAAAAAA">41</qs:Td>
<qs:Td>42</qs:Td>
<qs:Td>43</qs:Td>
<qs:Td>44</qs:Td>
<qs:Td Width="150">45</qs:Td>
<qs:Td>46</qs:Td>
</qs:Tr>
</qs:Table>

4.5 课表

<qs:Table
Width="600"
Height="250"
Align="Center"
Border="1 solid black collapse"
Valign="Middle">
<qs:Tr
Height="60"
Align="Center"
BgColor="#FFE5E5E5"
Valign="Middle">
<qs:Th ColSpan="2">
<TextBlock Text="课时/日期" />
</qs:Th>
<qs:Th>星期一</qs:Th>
<qs:Th>星期二</qs:Th>
<qs:Th>星期三</qs:Th>
<qs:Th>星期四</qs:Th>
<qs:Th>星期五</qs:Th>
</qs:Tr>
<qs:Tr Align="Center" Valign="Middle">
<qs:Td RowSpan="4">上午</qs:Td>
<qs:Td Width="100">第1节</qs:Td>
<qs:Td />
<qs:Td />
<qs:Td />
<qs:Td />
<qs:Td />
</qs:Tr>
<qs:Tr Align="Center" Valign="Middle">
<qs:Td>第2节</qs:Td>
<qs:Td />
<qs:Td />
<qs:Td />
<qs:Td />
<qs:Td />
</qs:Tr>
<qs:Tr Align="Center" Valign="Middle">
<qs:Td>第3节</qs:Td>
<qs:Td />
<qs:Td />
<qs:Td />
<qs:Td />
<qs:Td />
</qs:Tr>
<qs:Tr Align="Center" Valign="Middle">
<qs:Td>第4节</qs:Td>
<qs:Td />
<qs:Td />
<qs:Td />
<qs:Td />
<qs:Td />
</qs:Tr>
<qs:Tr Align="Center" Valign="Middle">
<qs:Td RowSpan="2">上午</qs:Td>
<qs:Td Width="100">第5节</qs:Td>
<qs:Td />
<qs:Td />
<qs:Td />
<qs:Td />
<qs:Td />
</qs:Tr>
<qs:Tr Align="Center" Valign="Middle">
<qs:Td>第6节</qs:Td>
<qs:Td />
<qs:Td />
<qs:Td />
<qs:Td />
<qs:Td />
</qs:Tr>
</qs:Table>

说明:文中用“像素”代替尺寸单位是为了便于理解,实际上WPF使用的是设备无关的尺寸单位,请注意分辨。

--------完结---------

技术交流群

联系方式

前言

相信很多做WPF开发的小伙伴都遇到过表格类的需求,虽然现有的Grid控件也能实现,但是使用起来的体验感并不好,比如要实现一个Excel中的表格效果,估计你能想到的第一个方法就是套Border控件,用这种方法你需要控制每个Border的边框,并且在一堆Bordr中找到Grid.Row,Grid.Column来确定位置,明明很简单的一个功能,硬是耗费了大量时间。Grid的这种设计虽然功能很强大,但是同时也导致了操作繁琐可读性非常差的问题。此时做过web开发的人肯定很想念html中的table元素,没错,我也是这样想的,如果能把html中的table元素搬到WPF中,那问题就轻松解决了,今天我们就来解决这个问题。

一、准备工作

我们先来认识一下table元素,其实最开始的网页功能相对简单,table元素主要用于展示文本和基本的排版。然而随着html标准的更新,table元素越来越复杂,很多功能在不同的标准中写法可能不一样,甚至有的功能只能在css中实现,这种情况我们成全照搬html中的写法肯定不现实,也完全没必要。所以必须做一个取舍。由于WPF中并没有css的概念,所以我们尽量舍弃css中的写法,使用WPF中类似的属性写法来开发,以下为统计出来的可用属性。

二、需求分析

既然我们要复刻一个东西,第一步肯定是要先搞清楚这个东西的内在逻辑,所以我们先来看看html中的table元素是怎么回事。

WPF实现html中的table控件的更多相关文章

  1. 如何在ElementUI中的Table控件中使用拼音进行排序

    本人使用版本是1.4.7 在这个版本中对应全是String的column进行排序并不是按照拼音的方式排列的. 这里我贴一下源代码就可以看出是为什么了: export const orderBy = f ...

  2. 在WPF中使用WinForm控件方法

    1.      首先添加对如下两个dll文件的引用:WindowsFormsIntegration.dll,System.Windows.Forms.dll. 2.      在要使用WinForm控 ...

  3. WPF中的image控件的Source赋值

    WPF中的Image控件Source的设置 1.XAML中 简单的方式(Source="haha.png"); image控件的Source设置为相对路径后(Source=&quo ...

  4. 在WPF中调用Winform控件

    最近在项目中用到了人脸识别和指纹识别,需要调用外部设备和接口,这里就用到了在WPF中调用Winform控件. 第一步,添加程序集引用.System.Windows.Forms和WindowsForms ...

  5. WPF中的ControlTemplate(控件模板)(转)

    原文地址 http://www.cnblogs.com/zhouyinhui/archive/2007/03/28/690993.html WPF中的ControlTemplate(控件模板)     ...

  6. [转]在WPF中使用WinForm控件方法

    本文转自:http://blog.csdn.net/lianchangshuai/article/details/6415241 下面以在Wpf中添加ZedGraph(用于创建任意数据的二维线型.条型 ...

  7. WPF中禁止WebBrowser控件打开新窗口

    一.针对纯WPF的WebBrowser控件: <summary> Suppress Script Errors In WPF WebBrowser </summary> pub ...

  8. 解决SMARTFORMS 中table 控件单行跨页的问题

    在CX项目中,MM模块做了大量的的单据打印的工作,一个问题困扰了我好久,一直不能解决.当物料描述很长时,table控件在单元格中能自动换行,这样就有可能在换页处出现一行记录的一部分打在上一页,一部分记 ...

  9. WPF中嵌入WinForm中的webbrowser控件

    原文:WPF中嵌入WinForm中的webbrowser控件 使用VS2008创建WPF应用程序,需使用webbrowser.从工具箱中添加WPF组件中的webbrowser发现其中有很多属性事件不能 ...

  10. WindowsXamlHost:在 WPF 中使用 UWP 控件库中的控件

    在 WindowsXamlHost:在 WPF 中使用 UWP 的控件(Windows Community Toolkit) 一文中,我们说到了在 WPF 中引入简单的 UWP 控件以及相关的注意事项 ...

随机推荐

  1. hdparm 常用命令介绍

    hdparm命令介绍 通常情况下可以使用fdisk.df等命令查看硬盘的分区情况以及当前已使用空间大小.剩余空间大小等信息.但是如果要查看硬盘的硬件信息如 硬盘型号.序列号.已运行时间等信息该用什么工 ...

  2. python基础语法知识

    1.多组输入没有结束标志的两种表示形式 #method1: try: while True: #代码 except EOFError: pass #method2: while True: try: ...

  3. 对yuv存储格式中的yuv420p和yuv420sp的理解

    一.对yuv的认识 yuv是一种颜色编码系统,它将图像的亮度和色度分离开来.y表示亮度,即黑白信息:uv表示色度,即颜色信息.yuv常用于视频压缩和传输中,因为它可以更有效地表示人眼对亮度和色度的敏感 ...

  4. OFDM系统各种调制阶数的QAM误码率(Symbol Error Rate)与 误比特率(Bit Error Rate)仿真结果

    本文是OFDM系统的不同QAM调制阶数的误码率与误比特率仿真,仅考虑在高斯白噪声信道下的情景,着重分析不同信噪比下的误码(符号)率性能曲线,不关心具体的调制与解调方案,仿真结果与理论的误码率曲线进行了 ...

  5. 【Azure 环境】移动应用 SSO 登录AAD, MSAL的配置为Webview模式时登录页面无法加载

    问题描述 移动端集成MASL登录过程中,配置文件中配置项"authorization_user_agent"使用"DEFAULT"可以正常登录,但是改为&quo ...

  6. 【Azure 应用服务】基于Azure的CI/CD工具链部署App Service

    问题描述 在中国区Azure中,App Service是否支持CI/CD工具部署呢? Windows 和Linux两个系统都是同样的方法吗? 问题解答 目前中国区Azure支持Windows 和 Li ...

  7. Java UML类图

    在UML的静态机制中类图是一个重点,它不但是设计人员关心的核心,更是实现人员关注的核心.建模工具也主要根据类图来产生代码.类图在UML的9个图中占据了一个相当重要的地位.James Rumbaugh对 ...

  8. Kubernetes CKA考试之Killer Simulator(上)

    写在前面 个人微信公众号:密码应用技术实战 个人博客园首页:https://www.cnblogs.com/informatics/ 注:学习交流使用 CKA Simulator Kubernetes ...

  9. 深入解析:AntSK 0.1.7版本的技术革新与多模型管理策略

    在信息技术快速迭代的当下,.Net生态中的AntSK项目凭借其前沿的AI知识库和智能体技术,已经吸引了广大开发者的关注和参与.今天,我要给大家介绍的主角,AntSK 0.1.7版本,无疑将是这个开源项 ...

  10. linux下,使用nginx实现动静分离,访问图片报404

    一.需求描述 最近在开发一个微信小程序,由于微信小程序端代码包总大小限定在三四兆,所以有很多的图标资源就不能放在微信小程序中进行打包, 否则会超过微信的限制而无法打包.自己能够想到的最简单的办法就是将 ...