In our previous article we described how to display the table with the dynamic structure.
At this point a user may be puzzled with reducing of the table columns width, and it really seems quite logical. Basically, one of the approaches to do this, would be reducing the columns width in a template, but here we would come across a couple of problems. On one hand, we can apply our modification only to all table columns at once, and on the other hand, we cannot predict to what extent the values can grow and how big the numbers can become, thus insufficient cell space to store one of such values would make the table view unacceptable. The same thing can happen to the column that stores the parameter names. What if we should output the parameter called ‘TheReallyLongParameterName’? Most probably, the parameter name won’t fit the cell size as well. So puzzled by the question to find a workaround, we can say for sure this is a feasible task when some useful advice is available at your hand. We only need to know how to calculate the columns width. That is what we are going to talk about further in this article.
Prologue to Report Rendering
It is very important to understand the mechanics involved in report rendering. Basically, everything is really simple: all the report template controls are rendered vertically from left to right. The DataBand and the CrossBand are rendered alternately from the very first record to the last one. One interesting fact is that PageHeader and PageFooter controls are the first elements in the rendering list, thus they are rendered prior to the main content. Generally, this will not affect our task under study, but you should bear this information in mind when creating the report structure.
The simple and obvious things however have some background aspects we should be aware of:
1. At the moment of rendering the subsequent control, the previous control has already been rendered
2. At the moment of rendering the subsequent record, the previous record has already been rendered
3. If the control has already been rendered, none of the template modifications would affect it
So how does this fact can influence our task? The matter is that we are unable to calculate the row size during the rendering in case we would like to have all table columns equally sized. To tell the truth, we have two available approaches in such situation:
1. We can perform the rendering in two passes. We calculate the width during the first pass, and then use this calculated data on our second pass. The disadvantage of such approach is that our first pass has already been used, and we have no option for other situations that may require it, since we are limited only to two passes.
2. The second approach can look a bit more complex than the previous one, but it is still feasible. The idea is in iterating through the columns to calculate the size before the rendering. The main disadvantage of this approach is that more effort should be made in order to make everything work. You should implement the logic used for work with data source which would result in lost flexibility.
Calculating the Text Width
The text width is supposed to be calculated with the help of GDI+ function Graphics.MeasureString.
In general, the function used for width calculation looks as follows (Note: this function should be added to CommonScript of the document):
- double CalculateWidth(TextBox textBox, string text)
- {
- var font = textBox.Font.GetFont();
- return Engine.GetGraphics().MeasureString(text, font).Width;
- }
As we can see, to get the font and the text which should be measured, a textbox should be passed to the method. The Graphics object is received from the Engine by calling GetGraphics method.
We are ready to go
Now we have everything required for report modification.
To begin with, we declare a couple of variables in the Document.CommonScript.
- System.Collections.Generic.Dictionary<int, double> columnWidths = new System.Collections.Generic.Dictionary<int, double>();
- double firstColumnWidth;
The first one will store the width for all report columns, while the other one stores the column width together with the parameter name. Why not to store all this in a single dictionary? The fact is that the first column is being stored and output separately from the others and, that is one of the moments that prevent us from adding everything to a single dictionary.
Then we should add the previously mentioned function to calculate the text width.
And then, we add the function to calculate all the rows and find out the optimum width for every column.
- void CalculateColumnWidths()
- {
- // Getting datasource
- CrossBandSample.TableData data = DataObjects["data"] as CrossBandSample.TableData;
- // Calculating widths for the header
- firstColumnWidth = CalculateWidth(textBoxTopLeft, textBoxTopLeft.Text);
- for(int i = 0;i < data.ColumnNames.Count;i++)
- {
- columnWidths.Add(i, CalculateWidth(textBoxColumn, data.ColumnNames[i]));
- }
- // Calculating widths for the table body cells
- for(int i = 0;i < data.Rows.Count;i++)
- {
- firstColumnWidth = Math.Max(firstColumnWidth,
- CalculateWidth(textBoxName, data.Rows[i].Name));
- for(int j = 0;j < data.ColumnNames.Count;j++)
- {
- columnWidths[j] = Math.Max(columnWidths[j],
- CalculateWidth(textBoxValue, data.Rows[i].Values[j].ToString(“0.00″)));
- }
- }
- }
It really should not be a challenge to see into the code of this function. The only thing we would like to mention here is that the DataObjects collection stores a collection of data sources and makes possible getting these data sources by names.
Now we can call our calculation function from the Document.GenerateScript:
- CalculateColumnWidths();
Finally, we got the required column size prior to starting the table rendering and, all we have to do now is to use this data:
- crossBandColumn. GenerateScript:
- crossBandColumn.Size = new Vector(columnWidths[crossBandColumn.LineNumber], crossBandColumn.Size.Height);
- textBoxColumn.Size = crossBandColumn.Size;
- crossBandTopLeft.GenerateScript:
- crossBandTopLeft.Size = new Vector(firstColumnWidth, crossBandTopLeft.Size.Height);
- textBoxTopLeft.Size = crossBandTopLeft.Size;
- crossBandValue. GenerateScript:
- crossBandValue.Size = new Vector(columnWidths[crossBandValue.LineNumber], crossBandValue.Size.Height);
- textBoxValue.Size = crossBandValue.Size;
- crossBandName. GenerateScript:
- crossBandName.Size = new Vector(firstColumnWidth, crossBandName.Size.Height);
- textBoxName.Size = crossBandName.Size;
The result looks as expected.