翼度科技»论坛 编程开发 .net 查看内容

WPF实现html中的table控件

8

主题

8

帖子

24

积分

新手上路

Rank: 1

积分
24
前言
  相信很多做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结构
 
  1. <table>
  2.   <tr>
  3.     <th>header1</th>
  4.     <th>header2</th>
  5.     <th>header3</th>
  6.   </tr>
  7.   <tr>
  8.     <td>value1</td>
  9.     <td>value2</td>
  10.     <td>value3</td>
  11.   </tr>
  12.   <tr>
  13.     <td>value4</td>
  14.     <td>value5</td>
  15.     <td>value6</td>
  16.   </tr>
  17. </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 取值范围

    • 百度比(例:width="50%")
    • 像素(例:width="500")
    • 不设置(自动计算)
       

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的示例代码。
  1. [ContentProperty("Rows")]
  2. public class Table : UIElement
  3. {
  4.     /// <summary>
  5.     /// 获取或设置行
  6.     /// </summary>
  7.     public TrCollection Rows
  8.     {
  9.         get { return (TrCollection)GetValue(RowsProperty); }
  10.         private set { SetValue(RowsProperty, value); }
  11.     }
  12.     public static readonly DependencyProperty RowsProperty =
  13.         DependencyProperty.Register("Rows", typeof(TrCollection), typeof(Table));
  14.     /// <summary>
  15.     /// 获取或设置宽度
  16.     /// </summary>
  17.     public TableLength Width
  18.     {
  19.         get { return (TableLength)GetValue(WidthProperty); }
  20.         set { SetValue(WidthProperty, value); }
  21.     }
  22.     public static readonly DependencyProperty WidthProperty =
  23.         DependencyProperty.Register("Width", typeof(TableLength), typeof(Table));
  24.     /// <summary>
  25.     /// 获取或设置高度
  26.     /// </summary>
  27.     public TableLength Height
  28.     {
  29.         get { return (TableLength)GetValue(HeightProperty); }
  30.         set { SetValue(HeightProperty, value); }
  31.     }
  32.     public static readonly DependencyProperty HeightProperty =
  33.         DependencyProperty.Register("Height", typeof(TableLength), typeof(Table));
  34. }
复制代码
 
3.2 Tr
    Tr在Table里主要的作用是表达逻辑关系,不需要在界面上呈现,所以我们可以让它继承自DependencyObject,可以绑定属性就行了,以下为示例代码。
  1. [ContentProperty("Cells")]
  2. public class Tr : DependencyObject
  3. {
  4.     /// <summary>
  5.     /// 获取或设置单元格
  6.     /// </summary>
  7.     public TdCollection Cells
  8.     {
  9.         get { return (TdCollection)GetValue(CellsProperty); }
  10.         private set { SetValue(CellsProperty, value); }
  11.     }
  12.     public static readonly DependencyProperty CellsProperty =
  13.         DependencyProperty.Register("Cells", typeof(TdCollection), typeof(Tr));
  14.     /// <summary>
  15.     /// 获取或设置高度
  16.     /// </summary>
  17.     public TableLength Height
  18.     {
  19.         get { return (TableLength)GetValue(HeightProperty); }
  20.         set { SetValue(HeightProperty, value); }
  21.     }
  22.     public static readonly DependencyProperty HeightProperty =
  23.         DependencyProperty.Register("Height", typeof(TableLength), typeof(Tr));
  24. }
复制代码
 
3.3 Td
    Td的情况与Table类似,需要在界面上呈现,并且有自己的尺寸及布局逻辑,所以继承自UIElement,以下为示例代码。
  1. public class Td : UIElement
  2. {
  3.     /// <summary>
  4.     /// 获取或设置需要跨的列数
  5.     /// </summary>
  6.     public int ColSpan
  7.     {
  8.         get { return (int)GetValue(ColSpanProperty); }
  9.         set { SetValue(ColSpanProperty, value); }
  10.     }
  11.     public static readonly DependencyProperty ColSpanProperty =
  12.         DependencyProperty.Register("ColSpan", typeof(int), typeof(Td), new PropertyMetadata(1));
  13.     /// <summary>
  14.     /// 获取或设置需要跨的行数
  15.     /// </summary>
  16.     public int RowSpan
  17.     {
  18.         get { return (int)GetValue(RowSpanProperty); }
  19.         set { SetValue(RowSpanProperty, value); }
  20.     }
  21.     public static readonly DependencyProperty RowSpanProperty =
  22.         DependencyProperty.Register("RowSpan", typeof(int), typeof(Td), new PropertyMetadata(1));
  23.     /// <summary>
  24.     /// 获取或设置宽度
  25.     /// </summary>
  26.     public TableLength Width
  27.     {
  28.         get { return (TableLength)GetValue(WidthProperty); }
  29.         set { SetValue(WidthProperty, value); }
  30.     }
  31.     public static readonly DependencyProperty WidthProperty =
  32.         DependencyProperty.Register("Width", typeof(TableLength), typeof(Table));
  33.     /// <summary>
  34.     /// 获取或设置高度
  35.     /// </summary>
  36.     public TableLength Height
  37.     {
  38.         get { return (TableLength)GetValue(HeightProperty); }
  39.         set { SetValue(HeightProperty, value); }
  40.     }
  41.     public static readonly DependencyProperty HeightProperty =
  42.         DependencyProperty.Register("Height", typeof(TableLength), typeof(Table));
  43. }
复制代码
 
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 默认效果
  1. <qs:Table
  2.     Width="50%"
  3.     Height="50%"
  4.     Align="Center"
  5.     Border="1 solid red"
  6.     Valign="Middle">
  7.     <qs:Tr>
  8.         <qs:Th>11</qs:Th>
  9.         <qs:Th>12</qs:Th>
  10.         <qs:Th>13</qs:Th>
  11.         <qs:Th>14</qs:Th>
  12.         <qs:Th>15</qs:Th>
  13.         <qs:Th>16</qs:Th>
  14.     </qs:Tr>
  15.     <qs:Tr>
  16.         <qs:Td>21</qs:Td>
  17.         <qs:Td>22</qs:Td>
  18.         <qs:Td>23</qs:Td>
  19.         <qs:Td>24</qs:Td>
  20.         <qs:Td>25</qs:Td>
  21.         <qs:Td>26</qs:Td>
  22.     </qs:Tr>
  23.     <qs:Tr>
  24.         <qs:Td>31</qs:Td>
  25.         <qs:Td>32</qs:Td>
  26.         <qs:Td>33</qs:Td>
  27.         <qs:Td>34</qs:Td>
  28.         <qs:Td>35</qs:Td>
  29.         <qs:Td>36</qs:Td>
  30.     </qs:Tr>
  31.     <qs:Tr>
  32.         <qs:Td>41</qs:Td>
  33.         <qs:Td>42</qs:Td>
  34.         <qs:Td>43</qs:Td>
  35.         <qs:Td>44</qs:Td>
  36.         <qs:Td>45</qs:Td>
  37.         <qs:Td>46</qs:Td>
  38.     </qs:Tr>
  39. </qs:Table>
复制代码
 
4.2 合并相邻的线

 
  1. <qs:Table
  2.     Width="50%"
  3.     Height="50%"
  4.     Align="Center"
  5.     Border="1 solid red"
  6.     Valign="Middle">
  7.     <qs:Tr>
  8.         <qs:Th>11</qs:Th>
  9.         <qs:Th>12</qs:Th>
  10.         <qs:Th>13</qs:Th>
  11.         <qs:Th>14</qs:Th>
  12.         <qs:Th>15</qs:Th>
  13.         <qs:Th>16</qs:Th>
  14.     </qs:Tr>
  15.     <qs:Tr>
  16.         <qs:Td>21</qs:Td>
  17.         <qs:Td>22</qs:Td>
  18.         <qs:Td>23</qs:Td>
  19.         <qs:Td>24</qs:Td>
  20.         <qs:Td>25</qs:Td>
  21.         <qs:Td>26</qs:Td>
  22.     </qs:Tr>
  23.     <qs:Tr>
  24.         <qs:Td>31</qs:Td>
  25.         <qs:Td>32</qs:Td>
  26.         <qs:Td>33</qs:Td>
  27.         <qs:Td>34</qs:Td>
  28.         <qs:Td>35</qs:Td>
  29.         <qs:Td>36</qs:Td>
  30.     </qs:Tr>
  31.     <qs:Tr>
  32.         <qs:Td>41</qs:Td>
  33.         <qs:Td>42</qs:Td>
  34.         <qs:Td>43</qs:Td>
  35.         <qs:Td>44</qs:Td>
  36.         <qs:Td>45</qs:Td>
  37.         <qs:Td>46</qs:Td>
  38.     </qs:Tr>
  39. </qs:Table>
复制代码
 
4.3 合并单元格

 
  1. <qs:Table
  2.     Width="50%"
  3.     Height="50%"
  4.     Align="Center"
  5.     Border="1 solid red collapse"
  6.     Valign="Middle">
  7.     <qs:Tr>
  8.         <qs:Th>11</qs:Th>
  9.         <qs:Th>12</qs:Th>
  10.         <qs:Th>13</qs:Th>
  11.         <qs:Th>14</qs:Th>
  12.         <qs:Th>15</qs:Th>
  13.         <qs:Th>16</qs:Th>
  14.     </qs:Tr>
  15.     <qs:Tr>
  16.         <qs:Td ColSpan="6">21</qs:Td>
  17.     </qs:Tr>
  18.     <qs:Tr>
  19.         <qs:Td RowSpan="2">31</qs:Td>
  20.         <qs:Td>32</qs:Td>
  21.         <qs:Td ColSpan="2" RowSpan="2">33</qs:Td>
  22.         <qs:Td>35</qs:Td>
  23.         <qs:Td RowSpan="2">36</qs:Td>
  24.     </qs:Tr>
  25.     <qs:Tr>
  26.         <qs:Td>42</qs:Td>
  27.         <qs:Td>45</qs:Td>
  28.     </qs:Tr>
  29. </qs:Table>
复制代码
 
4.4 综合案例

  
  1. <qs:Table
  2.     Width="50%"
  3.     Height="50%"
  4.     Align="Center"
  5.     Border="1 solid red"
  6.     Valign="Middle">
  7.     <qs:Tr>
  8.         <qs:Th>11</qs:Th>
  9.         <qs:Th>12</qs:Th>
  10.         <qs:Th>13</qs:Th>
  11.         <qs:Th>14</qs:Th>
  12.         <qs:Th>15</qs:Th>
  13.         <qs:Th>16</qs:Th>
  14.     </qs:Tr>
  15.     <qs:Tr>
  16.         <qs:Td>21</qs:Td>
  17.         <qs:Td>22</qs:Td>
  18.         <qs:Td>23</qs:Td>
  19.         <qs:Td>24</qs:Td>
  20.         <qs:Td>25</qs:Td>
  21.         <qs:Td>26</qs:Td>
  22.     </qs:Tr>
  23.     <qs:Tr>
  24.         <qs:Td>31</qs:Td>
  25.         <qs:Td>32</qs:Td>
  26.         <qs:Td>33</qs:Td>
  27.         <qs:Td>34</qs:Td>
  28.         <qs:Td>35</qs:Td>
  29.         <qs:Td>36</qs:Td>
  30.     </qs:Tr>
  31.     <qs:Tr>
  32.         <qs:Td>41</qs:Td>
  33.         <qs:Td>42</qs:Td>
  34.         <qs:Td>43</qs:Td>
  35.         <qs:Td>44</qs:Td>
  36.         <qs:Td>45</qs:Td>
  37.         <qs:Td>46</qs:Td>
  38.     </qs:Tr>
  39. </qs:Table>
复制代码
 
4.5 课表

 
  1. <qs:Table
  2.     Width="600"
  3.     Height="250"
  4.     Align="Center"
  5.     Border="1 solid black collapse"
  6.     Valign="Middle">
  7.     <qs:Tr
  8.         Height="60"
  9.         Align="Center"
  10.         BgColor="#FFE5E5E5"
  11.         Valign="Middle">
  12.         <qs:Th ColSpan="2">
  13.             <TextBlock Text="课时/日期" />
  14.         </qs:Th>
  15.         <qs:Th>星期一</qs:Th>
  16.         <qs:Th>星期二</qs:Th>
  17.         <qs:Th>星期三</qs:Th>
  18.         <qs:Th>星期四</qs:Th>
  19.         <qs:Th>星期五</qs:Th>
  20.     </qs:Tr>
  21.     <qs:Tr Align="Center" Valign="Middle">
  22.         <qs:Td RowSpan="4">上午</qs:Td>
  23.         <qs:Td Width="100">第1节</qs:Td>
  24.         <qs:Td />
  25.         <qs:Td />
  26.         <qs:Td />
  27.         <qs:Td />
  28.         <qs:Td />
  29.     </qs:Tr>
  30.     <qs:Tr Align="Center" Valign="Middle">
  31.         <qs:Td>第2节</qs:Td>
  32.         <qs:Td />
  33.         <qs:Td />
  34.         <qs:Td />
  35.         <qs:Td />
  36.         <qs:Td />
  37.     </qs:Tr>
  38.     <qs:Tr Align="Center" Valign="Middle">
  39.         <qs:Td>第3节</qs:Td>
  40.         <qs:Td />
  41.         <qs:Td />
  42.         <qs:Td />
  43.         <qs:Td />
  44.         <qs:Td />
  45.     </qs:Tr>
  46.     <qs:Tr Align="Center" Valign="Middle">
  47.         <qs:Td>第4节</qs:Td>
  48.         <qs:Td />
  49.         <qs:Td />
  50.         <qs:Td />
  51.         <qs:Td />
  52.         <qs:Td />
  53.     </qs:Tr>
  54.     <qs:Tr Align="Center" Valign="Middle">
  55.         <qs:Td RowSpan="2">上午</qs:Td>
  56.         <qs:Td Width="100">第5节</qs:Td>
  57.         <qs:Td />
  58.         <qs:Td />
  59.         <qs:Td />
  60.         <qs:Td />
  61.         <qs:Td />
  62.     </qs:Tr>
  63.     <qs:Tr Align="Center" Valign="Middle">
  64.         <qs:Td>第6节</qs:Td>
  65.         <qs:Td />
  66.         <qs:Td />
  67.         <qs:Td />
  68.         <qs:Td />
  69.         <qs:Td />
  70.     </qs:Tr>
  71. </qs:Table>
复制代码
说明:文中用“像素”代替尺寸单位是为了便于理解,实际上WPF使用的是设备无关的尺寸单位,请注意分辨。
 
--------完结---------
技术交流群
 

 
 联系方式

 
 
 

 
来源:https://www.cnblogs.com/qushi2020/p/18096836
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具