I am using the Xceed Docx library to generate a Word document which contains a lot of tables with the following formatting
Expected
The problem is that the library seems to insert a spacing before the cells' first paragraph, which renders as follows
Actual rendering
Here's the code I use to generate the table
private Table InitTable(DocX document)
{
int rows = Util.ListNullOrEmpty(reponses) ? 3 : 2 + reponses.Count;
int columns = 6;
var table = document.AddTable(rows, columns);
table.Rows[0].MergeCells(4, 5);
table.Rows[0].Cells[0].Width = 34; // 12 mm
table.Rows[0].Cells[1].Width = 127.55; // 45 mm
table.Rows[0].Cells[2].Width = 104.88; // 37 mm
table.Rows[0].Cells[3].Width = 104.88; // 37 mm
table.Rows[0].Cells[4].Width = 104.88; // 37 mm
Border border = new Border(BorderStyle.Tcbs_thick, BorderSize.one, 10, System.Drawing.Color.Black);
List<string> enteteLigne1 = new List<string>
{
"Column 1", "Column 2", "Column 3", "Column 4", "Column 5"
};
// Header : First row
for (int i = 0; i < columns -1; i++)
{
SetCellBorder(table.Rows[0].Cells[i], border, 0b0000);
FormatCellContent(table.Rows[0].Cells[i], enteteLigne1[i], "Arial", 10d, Alignment.center);
}
return table;
}
private void FormatCellContent(Cell cell, string content, string fontName, double fontSize, Alignment alignment)
{
var p = cell.Paragraphs.FirstOrDefault();
if (p == null)
{
p = cell.InsertParagraph();
}
p.SpacingBefore(2.9); // 1 mm * 2.834645669 * 20 (OpenXML unit)
p.SpacingAfter(2.9);
p.Alignment = alignment;
p.Font(fontName);
p.FontSize(fontSize);
p.InsertText(content);
}
The only alternative is to insert OpenXml code into the paragraph's Xml property, but that would be tedious and somehow defies the purpose of using the library.
What did I do wrong ?
Thanks in advance
Instead of
table.Rows[0].Cells[0].Width = 34;
use
table.SetColumnWidth(0, 34);
which will define the column width of the table
Related
I have a Text file contains table contents such as following:
|ID |SN| | Date | Code |Comp|Source| Format |Unit|BuyQTY|DoneQTY|YetQTY|Late
21C011 5 1080201 BAO-99 高雄 10P056 5X3X5M/R RBDC-18865LA M 10000 7000 3000 1
21C006 1 1080201 BAO-99 高雄 20A001 5X8X2M/R 高廠軟 Q 料 M 60000 40000 20000 1
21C002 6 1080201 BAO-99 高雄 10W013 5X1X5M/R PVC+UV M 202000 100500 101500
21C006 4 1080212 BAO-99 高雄 10P038 4X5X5M/R DIGI PACK M 255000 255000
21C006 5 1080212 BAO-99 高雄 10P039 4X6X5M/R DIGI PACK 295000 295000
21C006 6 1080212 BAO-99 高雄 10P040 4X2X5M/R DIGI PACK M 114000 114000
21C006 7 1080212 BAO-99 高雄 10P041 4X9X5M/R DIGI PACK M 49500 49500
Notice that there are many missing values and different length in "Format" column.
I tried to read it into Excel such as following:
Because of the missing values and different format length, I can NOT just simply use Split.
I tried to use Graphics.MeasureString() to get the width of the substring between certain lengths.
Such as width between 125 and 140 will be "Unit".
But because of the Chinese characters and spaces, the result are all "crooked"!
I can never get it to the right column!
Could somebody please be so kind and teach me how could I get it done correctly!?
Much appreciated!!!
Update:
Because I'm writing a program for somebody to do such a task, so I CAN'T ask him to modify the original text through NotePad++ or any other software.
I also can NOT ask him to import it using Excel and set the column widths!
ALL because of it's for their convenience!!!
So I apologize VERY MUCH if I can NOT make life any easier!!!
PS. The Chinese characters are BIG5.
The following is the code I use to parse the text file into a DataGridView:
float[] colLens = new float[] { 137, 161, 301, 359, 400, 510, 760, 804, 872, 944, 1010, 1035,1050 };
Graphics g = CreateGraphics();
str = File.ReadAllLines(ofd.FileName,Encoding.GetEncoding("BIG5"));
for(int i = 0; i < str.Count(); i++)
{
int c = 0;
DataGridViewRow row = new DataGridViewRow();
row.CreateCells(dgvMain);
d = -1;
for(int j = 1; j < str[i].Length ; j++)
{
string s = str[i].Substring(0, j);
SizeF size = g.MeasureString(s, new Font("細明體", 12));
for (int k = d + 1; k < colLens.Count()-1; k++)
{
if (size.Width < colLens[k]) break;
else if(size.Width < colLens[k + 1])
{
d = k;
row.Cells[d].Value = str[i].Substring(c, j - c);
c = j;
break;
}
}
}
dgvMain.Rows.Add(row);
}
Chinese encodings are variable length, whether Big5 or GB18030. This means that Xis stored as a single byte but 高 is stored as two bytes. It seems that this file has a fixed byte length per field, not a fixed character length.
This means that code that expects a fixed character length won't be able to read this file easily. That includes Excel and probably every CSV handling library or code.
In the worst case, you can read the bytes directly from a file stream. Each set of bytes can be converted to a string using Encoding.ToString. You can get the Big5 encoding with Encoding.GetEncoding(950).
Encoding _big5=Encoding.GetEncoding(950);
byte[] _buffer=new byte[90];
public string GetField(FileStream stream,int offset, int length)
{
var read=stream.ReadBytes(_buffer,offset,length);
if(read>0)
{
return _big5.GetString(buffer,0,read);
}
else
{
return "";
}
}
//Quick & dirty way to skip to end
public void SkipToLineEnd(FileStream stream)
{
int c;
while((in=stream.ReadByte()>-1)
{
if (c==(int)'\n')
{
return;
}
}
}
You can construct a record from a line this way :
public MyRecord GetNextRecord(FileStream stream)
{
var record = new MyRecord
{
Id=GetField(stream,0,9),
...
//6 bytes, not just 4
Comp = GetField(stream,28,6),
..
//Start from 50, 16 bytes
Format = GetField(stream,50,16)
};
SkipToLineEnd(stream);
return myRecord;
}
You can write an iterator method that reads records this way until it reaches the end of file. A quick&dirty method to do that would be to check whether the Position of the stream is so close to the end that no full record can be produced, eg :
public IEnumerable<MyRecord> GetRecords(FileStream stream,int recordLength)
{
while(stream.Position < stream.Length - recordLength)
{
yield return GetRecordNextRecord(stream);
}
}
And use it like this :
var records=GetRecords(myStream,96);
foreach(var record in records)
{
....
}
This will take care of trailing newlines and possibly broken last lines.
To skip the header lines, just call SkipToLineEnd() as many times as needed.
You could use a library like EPPlus to generate an Excel file directly from this, eg
using (var p = new ExcelPackage())
{
var ws=p.Workbook.Worksheets.Add("MySheet");
ws.Cells.LoadFromCollection(records);
p.SaveAs(new FileInfo(#"c:\workbooks\myworkbook.xlsx"));
}
My two cents:
First use "Split" and read from origin up to source column, then from late to unit (note "reverse" order). What is left is format.
IE, if using fixed columns and ONLY format gives problems,
var colsIdToSource = line.left(200); //Assuming 200 is the sum of cols up to source
var colsUnitToLate = line.right(150); //idem from unit to late
var formatColumn = line.substring(200, line.length-150); // May need to adjust a char or less
Then you process the known columns.
Good luck :)
This task is not as easy as it looks. The code work with posted input. It may need a little adjustments. See code below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;
namespace ConsoleApplication100
{
class Program
{
const string FILENAME = #"c:\temp\test.csv";
static void Main(string[] args)
{
//|ID |SN| | Date | Code |Comp|Source| Format |Unit|BuyQTY|DoneQTY|YetQTY|Late
DataTable dt = new DataTable();
dt.Columns.Add("ID", typeof(string));
dt.Columns.Add("SN", typeof(string));
dt.Columns.Add("Date", typeof(string));
dt.Columns.Add("Code", typeof(string));
dt.Columns.Add("Comp", typeof(string));
dt.Columns.Add("Source", typeof(string));
dt.Columns.Add("Format", typeof(string));
dt.Columns.Add("Unit", typeof(string));
dt.Columns.Add("BuyQTY", typeof(int));
dt.Columns.Add("DoneQTY", typeof(int));
dt.Columns.Add("YetQTY", typeof(int));
dt.Columns.Add("Late", typeof(int));
StreamReader reader = new StreamReader(FILENAME, Encoding.Unicode);
string line = "";
int lineCount = 0;
while((line = reader.ReadLine()) != null)
{
if ((++lineCount > 1) && (line.Trim().Length > 0))
{
string leader = line.Substring(0, 30).Trim();
string source = line.Substring(31, 16).Trim();
string trailer = line.Substring(48).TrimStart();
string format = trailer.Substring(0, 12).TrimStart();
trailer = trailer.Substring(12).Trim();
DataRow newRow = dt.Rows.Add();
string[] splitLeader = leader.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
newRow["ID"] = splitLeader[0].Trim();
newRow["SN"] = splitLeader[1].Trim();
newRow["Date"] = splitLeader[2].Trim();
newRow["Code"] = splitLeader[3].Trim();
newRow["Comp"] = splitLeader[4].Trim();
newRow["Source"] = source;
newRow["Format"] = format;
newRow["Unit"] = trailer.Substring(0,4).Trim();
newRow["BuyQTY"] = int.Parse(trailer.Substring(4, 8));
string doneQTYstr = trailer.Substring(12, 8).Trim();
if (doneQTYstr.Length > 0)
{
newRow["DoneQTY"] = int.Parse(doneQTYstr);
}
if (trailer.Length <= 28)
{
newRow["YetQTY"] = int.Parse(trailer.Substring(20));
}
else
{
newRow["YetQTY"] = int.Parse(trailer.Substring(20,8));
newRow["late"] = int.Parse(trailer.Substring(28));
}
}
}
}
}
}
I have a table with sensors data. There are sensors with long names that do not fit into a cell.
I want to add three points to end of the long sensor names (like text-overflow: ellipsis in css). I want to do it flexible without hardcoded values. Because in the future number of columns may be different.
How can I do it?
I create table like this:
var table = new PdfPTable(columns.Length);
var widths = new List<float>();
for (var i = 0; i < columns.Length; i++)
{
widths.Add(1f);
}
table.SetWidths(widths.ToArray());
And fill it:
for (var i = 0; i < columns.Length; i++)
{
var cell = new PdfPCell(new Phrase(columns[i], tableDataFont));
cell.UseAscender = true;
cell.HorizontalAlignment = Element.ALIGN_CENTER;
cell.VerticalAlignment = Element.ALIGN_MIDDLE;
cell.BackgroundColor = new Color(204, 204, 204);
cell.MinimumHeight = 20f;
table.AddCell(cell);
}
Something like this should work...
var text = "testtesttesttesttest";
var maxLength = 7;
var displayname = text;
if (text.Length > maxLength)
{
displayname = text.Substring(0, maxLength) + "...";
}
Considering you didn't post any code of your own, I can only provide some logic.
What I do here is just take the real value (insert that into text).
Declare a maximum length you want the text in your fields to be (maxLength).
Create a storage variable to for manipulation of the name without losing the original data (in case you want to keep that).
Then check if the text is longer than the maximumlength and replace all that is too long with "...".
You can then return "displayname" to whatever field you want it to be in.
This most likely isn't what you're looking for, but it does answer your question as it is right now.
I'm entering data from a CSV file into a OpenOffice spreadsheet.
This code gets the a new sheet in a spreadsheet:
Public Spreadsheet getSpreadsheet(int sheetIndex, XComponent xComp)
{
XSpreadsheet xSheets = ((XSpreadsheetDocument)xComp).getSheets();
XIndexAccess xSheetIA = (XIndexAccess)xSheets;
XSpreadsheet XSheet = (XSpreadsheet)xSheetsA.getByIndex(sheetIndex).Value;
return XSheet;
}
I then have method that enters a list into a cell range one cell at a time. I want to be able to automatically set the column size for these cells. which is something like
string final DataCell;
Xspreadsheet newSheet = getSpreadsheet(sheetIndex, xComp);
int numberOfRecords = ( int numberOfColumns * int numberOfRows);
for(cellNumber = 0; cellNumber < numberOfrecords; cellNumber++)
{
XCell tableData = newSheet.getCellbyPosition(columnValue, rowValue);
((XText)tableData).setString(finalDataCell);
column Value++;
if(columnValue > = numberOfColumns)
{
rowVal++ column = 0;
}
}
After googling i have found the function:
columns.OptimalWidth = True on http://forum.openoffice.org/en/forum/viewtopic.php?f=20&t=31292
but im unsure on how to use this. Could anyone explain this further or think of another way to have the cell autofit?
I understand the comments in the code are in Spanish I think, but the code is in English. I ran the comments through Google translate so now they are in English. I copied it from here:
//Auto Enlarge col width
private void largeurAuto(string NomCol)
{
XCellRange Range = null;
Range = Sheet.getCellRangeByName(NomCol + "1"); //Recover the range, a cell is
XColumnRowRange RCol = (XColumnRowRange)Range; //Creates a collar ranks
XTableColumns LCol = RCol.getColumns(); // Retrieves the list of passes
uno.Any Col = LCol.getByIndex(0); //Extract the first Col
XPropertySet xPropSet = (XPropertySet)Col.Value;
xPropSet.setPropertyValue("OptimalWidth", new one.Any((bool)true));
}
What this does it this: First it gets the range name and then gets the first column. The real code, though, is XpropertySet being used, which is explained REALLY well here.
public void optimalWidth(XSpreadsheet newSheet)
{
// gets the used range of the sheet
XSheetCellCursor XCursor = newSheet.createCursor();
XUsedAreaCursor xUsedCursor = (XUsedAreaCursor)XCursor;
xUsedCursor.gotoStartOfUsedArea(true);
xUsedCursor.gotoEndOfUsedArea(true);
XCellRangeAddressable nomCol = (XCellRangeAddressable)xUsedCursor;
XColumnRowRange RCol = (XColumnRowRange)nomCol;
XTableColumns LCol = RCol.getColumns();
// loops round all of the columns
for (int i = 0; i < nomCol.getRangeAddress().EndColumn;i++)
{
XPropertySet xPropSet = (XPropertySet)LCol.getByIndex(i).Value;
xPropSet.setPropertyValue("OptimalWidth", new uno.Any(true));
}
}
I don't know the width of the texts in the textblock beforehand, and I want the the textblock to be aligned to the right like this, while still having the individual lines left-aligned:
Mr. Petersen |
Elmstreet 9 |
888 Fantastic City|
(| donotes the right edge of the document)
It should be simple, but I can't figure it out.
I've tried to put all the text in a paragraph and set paragraph.Alignment = Element.ALIGN_RIGHT, but this will rightalign the individual lines.
I've tried to put the paragraph in a cell inside a table, and rightalign the cell, but the cell just takes the full width of the table.
If I could just create a container that would take only the needed width, I could simply rightalign this container, so maybe that is really my question.
Just set the Paragraph.Align property:
using (Document document = new Document()) {
PdfWriter.GetInstance(
document, STREAM
);
document.Open();
for (int i = 1; i < 11; ++i) {
Paragraph p = new Paragraph(string.Format(
"Paragraph {0}", i
));
p.Alignment = Element.ALIGN_RIGHT;
document.Add(p);
}
}
It even works with a long string like this:
string longString = #"
iText ® is a library that allows you to create and manipulate PDF documents. It enables developers looking to enhance web- and other applications with dynamic PDF document generation and/or manipulation.
";
Paragraph pLong = new Paragraph(longString);
pLong.Alignment = Element.ALIGN_RIGHT;
document.Add(pLong);
EDIT:
After looking at the "picture" you drew...
It doesn't match with the title. The only way you can align individual Paragraph objects like your picture is if the "paragraph" does NOT exceed the Document object's "content" box (for a lack of a better term). In other words, you won't be able to get that type of alignment if the amount of text exceeds that which will fit on a single line.
With that said, if you want that type of alignment you need to:
Calculate the widest value from the collection of strings you intend to use.
Use that value to set a common left indentation value for the Paragraphs.
Something like this:
using (Document document = new Document()) {
PdfWriter.GetInstance(
document, STREAM
);
document.Open();
List<Chunk> chunks = new List<Chunk>();
float widest = 0f;
for (int i = 1; i < 5; ++i) {
Chunk c = new Chunk(string.Format(
"Paragraph {0}", Math.Pow(i, 24)
));
float w = c.GetWidthPoint();
if (w > widest) widest = w;
chunks.Add(c);
}
float indentation = document.PageSize.Width
- document.RightMargin
- document.LeftMargin
- widest
;
foreach (Chunk c in chunks) {
Paragraph p = new Paragraph(c);
p.IndentationLeft = indentation;
document.Add(p);
}
}
UPDATE 2:
After reading your updated question, here's another option that lets you add text to the left side of the "container":
string textBlock = #"
Mr. Petersen
Elmstreet 9
888 Fantastic City
".Trim();
// get the longest line to calcuate the container width
var widest = textBlock.Split(
new string[] {Environment.NewLine}
, StringSplitOptions.None
)
.Aggregate(
"", (x, y) => x.Length > y.Length ? x : y
)
;
// throw-away Chunk; used to set the width of the PdfPCell containing
// the aligned text block
float w = new Chunk(widest).GetWidthPoint();
PdfPTable t = new PdfPTable(2);
float pageWidth = document.PageSize.Width
- document.LeftMargin
- document.RightMargin
;
t.SetTotalWidth(new float[]{ pageWidth - w, w });
t.LockedWidth = true;
t.DefaultCell.Padding = 0;
// you can add text in the left PdfPCell if needed
t.AddCell("");
t.AddCell(textBlock);
document.Add(t);
Can anyone explain this one to me. I'm trying to set the sizes of a GridView.
int totalWidth = 550;
int subWidth = totalWidth / _module.Values.Count;
with 3 modules this comes to 183, and with 2 it's 275.
Later in the code I do a:
myGrid.HeaderStyle.HorizontalAlign = HorizontalAlign.Center;
myGrid.HeaderStyle.Width = subWidth;
Now I would have assumed the width of the columns would be 183px with 3 modules, or 275 with 2. Actual sizes are: 224 + 153 + 172 = 549. The headertext of column 1 is long, but if I force it to be 183px, why does it insist of being 224? Text is column 2 is short, column 3 medio long. There is still plenty of space for all 3 headers to be squeezed a bit.
Just to be clear - I'm also doing a:
myGrid.RowStyle.HorizontalAlign = HorizontalAlign.Center;
myGrid.RowStyle.Width = subWidth;
I see the same wrong sizes in both Firefox and Chrome. I cannot get it into my stupid head how I can set a width and the column decides on another value despite my intentions.
Method in question:
private void CreateGridView()
{
GridView myGrid = new GridView();
int totalWidth = 550;
int subWidth = totalWidth / _module.Values.Count;
#region DataTable
DataTable dataTable = new DataTable();
string[] dataArray = new string[_module.Values.Count];
for (int i = 0; i < _module.Values.Count; i++)
{
dataArray[i] = _module.Values[i].Value.ToString("n0"); ;
}
for (int i = 0; i < _module.Values.Count; i++)
{
dataTable.Columns.Add(_module.Values[i].Text.ToString());
}
dataTable.Rows.Add(dataArray);
myGrid.DataSource = dataTable;
#endregion
myGrid.Width = totalWidth;
myGrid.GridLines = GridLines.None;
myGrid.HeaderStyle.HorizontalAlign = HorizontalAlign.Center;
myGrid.HeaderStyle.Width = subWidth;
myGrid.RowStyle.HorizontalAlign = HorizontalAlign.Center;
myGrid.RowStyle.Width = subWidth;
myGrid.DataBind();
Controls.Add(myGrid);
}
Why not set CssClass on the GridView (as well as other children of the GridView such as HeaderStyle, RowStyle and so on) and set the width on that, rather than having your UI/design elements inside of compiled code?
I would create a stylesheet, or add to an existing stylesheet with a class declaration:
.myStyle {
width: 550px;
}
.myStyle2Modules {
width: 750px;
}
.myStyleLotsOfModules {
width: 1000px;
}
and then reference the class. You can then tweak the UI positions and widths on the fly by editing the CSS. You can programatically choose the CSS class in the code, but the actual definitions of those styles are in the stylesheet.
Let your code handle the functionality, nuts and bolts and implementation - let the stylesheet handle all the design elements such as colours, fonts, widths, borders and so on