WPF實(shí)現(xiàn)html中的table控件的示例代碼
前言
相信很多做WPF開(kāi)發(fā)的小伙伴都遇到過(guò)表格類(lèi)的需求,雖然現(xiàn)有的Grid控件也能實(shí)現(xiàn),但是使用起來(lái)的體驗(yàn)感并不好,比如要實(shí)現(xiàn)一個(gè)Excel中的表格效果,估計(jì)你能想到的第一個(gè)方法就是套Border控件,用這種方法你需要控制每個(gè)Border的邊框,并且在一堆Bordr中找到Grid.Row,Grid.Column來(lái)確定位置,明明很簡(jiǎn)單的一個(gè)功能,硬是耗費(fèi)了大量時(shí)間。Grid的這種設(shè)計(jì)雖然功能很強(qiáng)大,但是同時(shí)也導(dǎo)致了操作繁瑣可讀性非常差的問(wèn)題。此時(shí)做過(guò)web開(kāi)發(fā)的人肯定很想念html中的table元素,沒(méi)錯(cuò),我也是這樣想的,如果能把html中的table元素搬到WPF中,那問(wèn)題就輕松解決了,今天我們就來(lái)解決這個(gè)問(wèn)題。
一、準(zhǔn)備工作
我們先來(lái)認(rèn)識(shí)一下table元素,其實(shí)最開(kāi)始的網(wǎng)頁(yè)功能相對(duì)簡(jiǎn)單,table元素主要用于展示文本和基本的排版。然而隨著html標(biāo)準(zhǔn)的更新,table元素越來(lái)越復(fù)雜,很多功能在不同的標(biāo)準(zhǔn)中寫(xiě)法可能不一樣,甚至有的功能只能在css中實(shí)現(xiàn),這種情況我們成全照搬html中的寫(xiě)法肯定不現(xiàn)實(shí),也完全沒(méi)必要。所以必須做一個(gè)取舍。由于WPF中并沒(méi)有css的概念,所以我們盡量舍棄css中的寫(xiě)法,使用WPF中類(lèi)似的屬性寫(xiě)法來(lái)開(kāi)發(fā),以下為統(tǒng)計(jì)出來(lái)的可用屬性。
二、需求分析
既然我們要復(fù)刻一個(gè)東西,第一步肯定是要先搞清楚這個(gè)東西的內(nèi)在邏輯,所以我們先來(lái)看看html中的table元素是怎么回事。
2.1 table結(jié)構(gòu)
<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內(nèi)可以放置多個(gè)tr。
2.1.2 tr
tr表示表格中的一行,一行可以放置若干個(gè)td。
2.1.3 td
td為表格單元格,td可以設(shè)置rowspan屬性合并多個(gè)行,可以設(shè)置colspan合并多個(gè)列。
2.2 尺寸單位
2.2.1適用范圍
table的width,height屬性,tr的height屬性,td的width,heigth屬性。
2.2.2 取值范圍
百度比(例:width="50%")
像素(例:width="500")
不設(shè)置(自動(dòng)計(jì)算)
2.3 布局邏輯
2.3.1 table
2.3.1.1 width="50%"
寬度占可用空間的50%,當(dāng)父控件尺寸改變時(shí)會(huì)重新計(jì)算寬度,如果所有td子元素的尺寸之合大于table寬度(width="50%"),table寬度==Sum(td.width)。
2.3.1.2 width="500"
寬度占500像素,當(dāng)父控件尺寸改變時(shí)不會(huì)重新計(jì)算寬度,如果所有td子元素的尺寸之合大于table寬度(width="500"),table寬度==Sum(td.width)。
2.3.1.3 不設(shè)置寬度
不設(shè)置寬度的情況下,寬度根據(jù)td子元素的寬度計(jì)算,Sum(td.width)。
2.3.2 tr
2.3.2.1 height="50%"
高度占table元素總高的50%,當(dāng)父控件尺寸改變時(shí)會(huì)重新計(jì)算高度,當(dāng)tr中高度最高的td超過(guò)了tr的50%時(shí),整行高度以該td的高度為準(zhǔn)。
2.3.2.2 height="500"
高度占500像素,當(dāng)父控件尺寸改變時(shí)會(huì)重新計(jì)算高度,當(dāng)tr中高度最高的td超過(guò)了tr的500像素時(shí),整行高度以該td的高度為準(zhǔn)。
2.3.2.3 不設(shè)置高度
不設(shè)置高度的情況下,以最高的td子元素為準(zhǔn)。
2.3.3 td
2.3.3.1 width="50%"
寬度占table寬度的50%,當(dāng)剩余寬度不足以分配給其它列時(shí)會(huì)壓縮該列的50%寬度,分配給其它列。該列的實(shí)際寬度以該列所有td的最大寬度為準(zhǔn)。
2.3.3.2 width="50"
寬度占50像素,當(dāng)剩余寬度不足以分配給其它列時(shí)會(huì)壓縮該列的50像素寬度,分配給其它列。該列的實(shí)際寬度以該列所有td的最大寬度為準(zhǔn)。
2.3.3.3 不設(shè)置寬度
不設(shè)置寬度的情況下,如果其它設(shè)置了寬度的列分配完寬度后,剩余寬度大于所有td的最小寬度的總合,那么未設(shè)置寬度的列會(huì)平均分配剩余的寬度,如果剩余的寬度小于所有td最小寬度的總合,那么所有td的寬度按最小寬度分配,其它已設(shè)置寬度的列則壓縮寬度。該列的實(shí)際寬度以該列所有td的最大寬度為準(zhǔn)。
2.3.3.4 height="50%"
高度占table高度的50%,當(dāng)剩余高度不足以分配給其它行時(shí)會(huì)壓縮該行的50%高度,分配給其它行。該行的實(shí)際高度以該行所有td的最大高度為準(zhǔn)。如果最高td的高度大于tr,則以最高的td為準(zhǔn),如果小于tr,則以tr的高度為準(zhǔn)。
2.3.3.5 height="50"
高度占50像素,當(dāng)剩余高度不足以分配給其它行時(shí)會(huì)壓縮該行的50像素高度,分配給其它行。該行的實(shí)際高度以該行所有td的最大高度為準(zhǔn)。如果最高td的高度大于tr,則以最高的td為準(zhǔn),如果小于tr,則以tr的高度為準(zhǔn)。
2.3.3.6 不設(shè)置高度
不設(shè)置高度的情況下,如果其它設(shè)置了高度的行分配完高度后,剩余高度大于所有td的最小高度的總合,那么未設(shè)置高度的行會(huì)平均分配剩余的高度,如果剩余的高度小于所有td最小高度的總合,那么所有td的高度按最小高度分配,其它已設(shè)置高度的行則壓縮高度。該行的實(shí)際高度以該列所有td的最大高度為準(zhǔn)。
三、功能實(shí)現(xiàn)
通過(guò)對(duì)需求的分析,我們知道至少應(yīng)該有3個(gè)類(lèi)來(lái)實(shí)現(xiàn)表格功能,分別是Table、Tr、Td,我們下面來(lái)看看怎么來(lái)實(shí)現(xiàn)它們。
3.1 Table控件
Table是一個(gè)在界面上需要呈現(xiàn)的元素,該控件主要處理布局及排列,不需要控件模板,所以不應(yīng)該繼承自Control類(lèi),那么可不可以繼承自Panel呢,明顯也不行,Panel的尺寸及布局系統(tǒng)繼承自FrameworkElement,并不能給它的寬度設(shè)置Width="50%"這種值,所以它不僅不能繼承自Panel,也不能繼承自FrameworkElement,所以Table應(yīng)該繼承自UIElement類(lèi),我們需要在Table寫(xiě)自己的尺寸及布局管理功能,以下為T(mén)albe的示例代碼。
[ContentProperty("Rows")] public class Table : UIElement { /// <summary> /// 獲取或設(shè)置行 /// </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> /// 獲取或設(shè)置寬度 /// </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> /// 獲取或設(shè)置高度 /// </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里主要的作用是表達(dá)邏輯關(guān)系,不需要在界面上呈現(xiàn),所以我們可以讓它繼承自DependencyObject,可以綁定屬性就行了,以下為示例代碼。
[ContentProperty("Cells")] public class Tr : DependencyObject { /// <summary> /// 獲取或設(shè)置單元格 /// </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> /// 獲取或設(shè)置高度 /// </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類(lèi)似,需要在界面上呈現(xiàn),并且有自己的尺寸及布局邏輯,所以繼承自UIElement,以下為示例代碼。
public class Td : UIElement { /// <summary> /// 獲取或設(shè)置需要跨的列數(shù) /// </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> /// 獲取或設(shè)置需要跨的行數(shù) /// </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> /// 獲取或設(shè)置寬度 /// </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> /// 獲取或設(shè)置高度 /// </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)); }
MeasureCore()
該方法傳入一個(gè)名為availableSize的Size參數(shù),該參數(shù)為控件可用的最大尺寸,我們需要通過(guò)這個(gè)參數(shù)計(jì)算各個(gè)單元格的排列位置及尺寸,并根據(jù)排列情況返回一個(gè)控件的期望尺寸,以下為實(shí)現(xiàn)的大致流程。
1 通過(guò)Table的Height及Width參數(shù)計(jì)算出真實(shí)的尺寸;
2 讀取Table的Tr屬性,取出所有Td,并定義一個(gè)二維數(shù)組將所有Td存放進(jìn)去(Td[n,n]),如果Td的RowSpan或ColSpan參數(shù)大于1則將被合并的位置存入一個(gè)null。
3 根據(jù)第2步的填充結(jié)果再定義一個(gè)存放坐標(biāo)的二維數(shù)據(jù)(Size[n,n]);
4 測(cè)量Td子元素的尺寸,計(jì)算每個(gè)單元格實(shí)際尺寸,根據(jù)Td子元素尺寸計(jì)算是否需要壓縮尺寸,計(jì)算完成后將單元格的尺寸存入第3步的數(shù)組中;
5 根據(jù)第3步保存的尺寸數(shù)據(jù)計(jì)算單元格跨行或跨列后的尺寸;
6 將計(jì)算出的Table實(shí)際尺寸返回給MeasureCore方法,以供下一步排列使用;
ArrangeCore()
該方法處理子控件的位置排列,循環(huán)調(diào)用每一個(gè)單元格的Arrange()方法,傳入測(cè)量位置及尺寸就可以了。
OnRender()
該方法讀取BorderColor、BgColor等參數(shù)畫(huà)線及填充顏色,表格的外觀都是由它畫(huà)出來(lái)的。
四、運(yùn)行效果
4.1 默認(rèn)效果
<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="課時(shí)/日期" /> </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節(jié)</qs:Td> <qs:Td /> <qs:Td /> <qs:Td /> <qs:Td /> <qs:Td /> </qs:Tr> <qs:Tr Align="Center" Valign="Middle"> <qs:Td>第2節(jié)</qs:Td> <qs:Td /> <qs:Td /> <qs:Td /> <qs:Td /> <qs:Td /> </qs:Tr> <qs:Tr Align="Center" Valign="Middle"> <qs:Td>第3節(jié)</qs:Td> <qs:Td /> <qs:Td /> <qs:Td /> <qs:Td /> <qs:Td /> </qs:Tr> <qs:Tr Align="Center" Valign="Middle"> <qs:Td>第4節(jié)</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節(jié)</qs:Td> <qs:Td /> <qs:Td /> <qs:Td /> <qs:Td /> <qs:Td /> </qs:Tr> <qs:Tr Align="Center" Valign="Middle"> <qs:Td>第6節(jié)</qs:Td> <qs:Td /> <qs:Td /> <qs:Td /> <qs:Td /> <qs:Td /> </qs:Tr> </qs:Table>
說(shuō)明:文中用“像素”代替尺寸單位是為了便于理解,實(shí)際上WPF使用的是設(shè)備無(wú)關(guān)的尺寸單位,請(qǐng)注意分辨。
以上就是WPF實(shí)現(xiàn)html中的table控件的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于WPF table控件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#判斷數(shù)據(jù)類(lèi)型的簡(jiǎn)單示例代碼
本篇文章要是對(duì)C#中判斷數(shù)據(jù)類(lèi)型的簡(jiǎn)單示例代碼進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01Winform啟動(dòng)另一個(gè)項(xiàng)目傳值的方法
這篇文章主要介紹了Winform啟動(dòng)另一個(gè)項(xiàng)目傳值的方法,通過(guò)調(diào)用進(jìn)程來(lái)實(shí)現(xiàn)項(xiàng)目之間的傳值,需要的朋友可以參考下2014-11-11Unity3D開(kāi)發(fā)教程:憤怒的小鳥(niǎo)
這篇文章詳細(xì)的講解了如何從0開(kāi)發(fā)出一個(gè)Unity3D的小游戲憤怒的小鳥(niǎo),本文包含大量的圖片與文字描述,也含有大量的源代碼,可以讓你快速入手,希望本篇文章對(duì)你有所幫助2021-06-06C#實(shí)現(xiàn)簡(jiǎn)單超市收銀系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)簡(jiǎn)單超市收銀系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02C# 無(wú)需COM組件創(chuàng)建快捷方式的實(shí)現(xiàn)代碼
做一個(gè)小程序, 需要?jiǎng)?chuàng)建快捷方式, 網(wǎng)上普遍的做法是引入 COM 組件, 雖然也挺方便的, 但引入之后, 程序就需要多帶一個(gè) dll 文件, 這樣, 想做成單文件便攜版就不行了2011-05-05C#編寫(xiě)的藝術(shù)字類(lèi)實(shí)例代碼
本文給大家分享使用純C#編寫(xiě)的藝術(shù)字類(lèi)實(shí)例代碼,代碼簡(jiǎn)單易懂,需要的朋友參考下本教程2016-03-03詳解C#設(shè)置Excel數(shù)據(jù)自適應(yīng)行高、列寬的2種情況
這篇文章主要介紹了C#設(shè)置Excel數(shù)據(jù)自適應(yīng)行高、列寬的2種情況,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04C# 無(wú)邊框窗體邊框陰影效果的簡(jiǎn)單實(shí)現(xiàn)
這篇文章介紹了C# 無(wú)邊框窗體邊框陰影效果的簡(jiǎn)單實(shí)現(xiàn),有需要的朋友可以參考一下2013-10-10