Silverlight 自定义Grid实线
(一)前言
由于项目的需求,必须用silverlight实现表格形式的预警图。在Silverlight中表格形式的最佳方式为Grid,虽然Grid提供了ShowGridLine属性,但是该线条为虚线,外观看起来很不协调。开始的时候,本人打算在每个Cell中采用Border来设置线条。这样虽然可以实现实线,但是每行每列都会涉及到单元格与单元格之间的重叠,以及第一列与最后一列、第一行与最后一行的线条控制需要复杂的算法来控制,因此这种方式肯定是行不通的(不是实现不了,而是将简单的问题复杂化了)。下面讲解的是另外一种方式,采用添加直线的方法来绘制实线。
(二)类图设计
下面所涉及的都是针对于特定情况下的Grid实线绘制,仅仅是本人项目中抽象出来的简单版本而已(仅仅是思路,具体实现涉及到信息安全,哥可扛不起,哈哈,现在的绝对与项目无关),类图如下:
实现的效果图如下:
(三)具体实现
这里主要可以控制第一行的高度,第一列的宽度,Grid在容器Canvas的间隔(LeftMargin,TopMargin),其他单元格的宽度、高度,Grid的行数,Grid的列数。至于实线的具体绘制封装在基类GridFormatter中,具体单元格的内容由子类来实现(可能用于很多场景),第一行的标题,第一列的内容都有子类AlertTable去实现。这里涉及到一些简单的数学运算,应该是很容易理解的。实现多态的话,只要实现抽象类GridFormatter的抽象方法SetTableContent即可。
基类GridFormatter的具体实现
(1)调用的方法主要是如下2个,主要参数都标识了,应该比较清晰易懂(至于判断参数是否小于0,这里就不打算写上去了,大家只要知道这些参数具体是什么就可以了):
2 /// 实例化GridFormatter类。
3 /// </summary>
4 /// <param name="leftMargin">左边距。</param>
5 /// <param name="topMargin">右边距。</param>
6 /// <param name="rowCount">行数。</param>
7 /// <param name="columnCount">列数。</param>
8 public GridFormatter(int leftMargin, int topMargin,
9 int rowCount,int columnCount)
10 {
11 this.LeftMargin = leftMargin;
12 this.TopMargin = topMargin;
13 this.RowCount = rowCount;
14 this.ColumnCount = columnCount;
15 }
16
17 /// <summary>
18 /// 创建Grid的内容。
19 /// </summary>
20 /// <param name="canvas">Canvas容器。</param>
21 /// <param name="cellWidth">单元格宽度。(第一行第一列的除外)</param>
22 /// <param name="cellHeight">单元格高度。(第一行第一列的除外)</param>
23 /// <param name="firstColumnWidth">第一列的宽度。</param>
24 /// <param name="firstRowHeight">第一行的高度。</param>
25 public void Create(Canvas canvas, int cellWidth, int cellHeight,
26 int firstColumnWidth, int firstRowHeight)
27 {
28 if (canvas == null)
29 {
30 throw new ArgumentNullException("The container's parameter is null!");
31 }
32
33 this.CellWidth = cellWidth;
34 this.CellHeight = cellHeight;
35 this.FirstColumnWidth = firstColumnWidth;
36 this.FirstRowHeight = firstRowHeight;
37
38 AddGrid(canvas);
39 CreateTable();
40 }
(2)接下来我们需要设置表格的架构,也就是说设置表格的行数和列数,以及这些单元格的宽度和高度,代码如下:
2 {
3 for (int rowIndex = 0; rowIndex < this.RowCount; rowIndex++)
4 {
5 int height = rowIndex == 0 ? this.FirstRowHeight : this.CellHeight;
6 RowDefinition rd = new RowDefinition();
7 rd.Height = new GridLength(height);
8 this.Grid.RowDefinitions.Add(rd);
9 }
10
11 for (int columnIndex = 0; columnIndex < this.ColumnCount; columnIndex++)
12 {
13 int width = columnIndex == 0 ? this.FirstColumnWidth : this.CellWidth;
14 ColumnDefinition cd = new ColumnDefinition();
15 cd.Width = new GridLength(width);
16 this.Grid.ColumnDefinitions.Add(cd);
17 }
18 }
如果是第一行,设置高度为this.FirstRowHeight。如果是第一列,设置宽度为this.FirstColumnWidth。具体由如下2行代码设置高度和宽度,
5 int height = rowIndex == 0 ? this.FirstRowHeight : this.CellHeight;
13 int width = columnIndex == 0 ? this.FirstColumnWidth : this.CellWidth;
(3)设置Grid的大小以及容器Canvas的大小(宽度和高度),因为Canvas必须设置大小才能正常显示,而且可以通过Canvas才能灵活的设置控件的绝对位置。代码如下:
2 {
3 this.Width = this.CellWidth * (this.ColumnCount - 1) + this.FirstColumnWidth;
4 this.Height = this.CellHeight * (this.RowCount - 1) + this.FirstRowHeight;
5 this.Container.Width = this.Width + this.LeftMargin * 2;
6 this.Container.Height = this.Height + this.TopMargin * 2;
7 }
下面的2行代码将使Grid在Canvas中居中显示:
5 this.Container.Width = this.Width + this.LeftMargin * 2;
6 this.Container.Height = this.Height + this.TopMargin * 2;
(4)设置水平线,代码如下:
2 {
3 AddLine(this.LeftMargin, this.TopMargin, this.Width + this.LeftMargin, this.TopMargin);
4
5 for (int index = 0; index < this.RowCount; index++)
6 {
7 int y1 = index * this.CellHeight + this.TopMargin + this.FirstRowHeight;
8 int y2 = y1;
9 AddLine(this.LeftMargin, y1, this.Width + this.LeftMargin, y2);
10 }
11 }
第三行 3 AddLine(this.LeftMargin, this.TopMargin, this.Width + this.LeftMargin, this.TopMargin);主要是绘制最上面的水平线。
for循环将设置其他的水平线。int y1 = index * this.CellHeight + this.TopMargin + this.FirstRowHeight;设置y坐标。因为从该行开始,y坐标将加上this.TopMargin(顶部间隔)以及 this.FirstRowHeight(第一行的高度)。
(5)设置竖线,代码如下:
2 {
3 AddLine(this.LeftMargin, this.TopMargin, this.LeftMargin, this.TopMargin + this.Height);
4
5 for (int index = 0; index < this.ColumnCount; index++)
6 {
7 int x1 = index * this.CellWidth + this.LeftMargin + this.FirstColumnWidth;
8 int x2 = x1;
9 AddLine(x1, this.TopMargin, x2, this.TopMargin + this.Height);
10 }
11 }
第三行 3 AddLine(this.LeftMargin, this.TopMargin, this.LeftMargin, this.TopMargin + this.Height);主要是绘制最左边的竖线。
for循环将设置其他的竖线。int x1 = index * this.CellWidth + this.LeftMargin + this.FirstColumnWidth;设置x坐标。因为从该列开始,x坐标将加上this.LeftMargin (左边间隔)以及 this.FirstColumnWidth(第一列的宽度)。
(6)实现单元格的内容
对于单元格具体内容的设置,将由子类实现public abstract void SetTableContent();
2 {
3 public AlertTable(int leftPadding, int topPadding,
4 int rowCount, int columnCount)
5 : base(leftPadding, topPadding, rowCount, columnCount)
6 {
7 }
8
9 public override void SetTableContent()
10 {
11 SetFirstRowContent();
12 SetFirstColumnContent();
13 SetContent();
14 }
15
16 private void SetContent()
17 {
18 for (int rowIndex = 1; rowIndex < base.RowCount; rowIndex++)
19 {
20 for (int columnIndex = 1; columnIndex < base.ColumnCount; columnIndex++)
21 {
22 //SetCellUserControl(rowIndex, columnIndex);
23 }
24 }
25 }
26
27 private void SetFirstColumnContent()
28 {
29 for (int index = 1; index < base.RowCount; index++)
30 {
31 base.SetCellTextContent(index, 0, "Row " + index.ToString());
32 }
33 }
34
35 private void SetFirstRowContent()
36 {
37 for (int index = 1; index < base.ColumnCount; index++)
38 {
39 base.SetCellTextContent(0, index, "Column Head " + index.ToString());
40 }
41 }
42 }
子类将重写父类的方法,如第9行:public override void SetTableContent();具体由子类来填充单元格的内容。
具体实现只有100行左右的代码,。
(四)总结
针对于容器中控件的绝对位置的控制采用Canvas(通常需要涉及到具体坐标点的控制),灵活运用各种控件来进行操作。对于Grid需要设置实线的话必须自定义绘制相关实线(目前Grid是虚线的,很不协调)。这种方式是由局限性的,只是针对于特定场景最有效。最近几个月跟着款哥混,水平直线飙升,多谢款哥。一个人有多优秀,要看他有谁指点,跟对人,做对事,至理名言啊,哈哈....
其实项目中哥还是没采用这种多态的方式,没必要,这样的场景只是偶尔出现,不需要多态。上面涉及的只是很简单的抽象,很多时候,代码需要不断的设计、重写来完善。有时候,方法也没必要设计得非常灵活,因为更加灵活的话意味着复杂性的提高。
源代码下载:/Files/jasenkin/Jasen.Silverlight.GridSample.rar