前言

  相信很多做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. NC14419 线路规划

    题目链接 题目 题目描述 Q国的监察院是一个神秘的组织. 这个组织掌握了整个帝国的地下力量,监察着Q国的每一个人. 监察院一共有N个成员,每一个成员都有且仅有1个直接上司,而他只听从其上直接司的命令. ...

  2. AIR32F103(二) Linux环境和LibOpenCM3项目模板

    目录 AIR32F103(一) 合宙AIR32F103CBT6开发板上手报告 AIR32F103(二) Linux环境和LibOpenCM3项目模板 AIR32F103(三) Linux环境基于标准外 ...

  3. 【Android逆向】脱壳项目frida_dump 原理分析

    脱dex核心文件dump_dex.js 核心函数 function dump_dex() { var libart = Process.findModuleByName("libart.so ...

  4. Git 分支管理参考模型

    一个值得参考的Git分支管理模型如下: master 生产主分支,发布到生产环境使用这个分支,由hotfix或者release分支合并过来,不直接提交代码. release 预发布分支, 基于feat ...

  5. OSG开发笔记(二十九):OSG加载模型文件、加载3DMax三维型文件Demo

    前言   Osg深入之后需要打开模型文件,这些模型文件是已有的模型文件,加载入osg之后可以在常见中展示模型文件,该节点可以操作,多个逼真的模型的节点就实现了基本的场景构建.   Demo      ...

  6. 记录一个错误:Traceback (most recent call last): from pip._internal.cli.main import main ImportError: No module named pip._internal.cli.main

    问题描述 在安装yaml时得到报错如下 root@ag-virtual-machine:/home/ag/test_script# pip install pyyaml Traceback (most ...

  7. 苏宁基于 AI 和图技术的智能监控体系的建设

    汤泳,苏宁科技集团智能监控与运维产研中心总监,中国商业联合会智库顾问,致力于海量数据分析.基于深度学习的时间序列分析与预测.自然语言处理和图神经网络的研究.在应用实践中,通过基于 AI 的方式不断完善 ...

  8. 用linux命令cd 查找想要找的文件

    如果想找文件Computer下的bin文件,在终端输入绝对路径 cd /bin,不能输入 cd /Computer/bin,因为文件目录不对 文件目录可以在文件的终端看到,/bin就是正确的目录 比如 ...

  9. 方便快速的看到C/C++代码汇编 objdump 英特尔语法

    目录 概述 Objdump 所有参数 其他的 概述 因为奇怪的考试要求,最近经常有奇怪的问题,例如为什么(++a)+(++a)=14 发现反编译出汇编之后,就能解释很多奇怪的问题 Objdump 一次 ...

  10. jenkins 上传文件参数

    注意:文件参数不支持Jenkins流水线 文件上传以后会上传至 workspace 里对应的project下面,但是文件会被重命名为File location(设置路径)输入库的值, 如果在jenki ...