Background
I am currently using Kendo-UI as my front-end library and I was using the saveAs method to generate reports based on the DOM that was being rendered, but ran into some serious performance issues (especially in IE). So I decided that I would handle the PDF generation on the server-side using Migradoc.
What I'm Trying To Do
Right now I have a listview (see demo) that essentially represents an image gallery. What I'm having a difficult time with is trying to figure out how to have the image/text go from left-to-right.
What I've Done
Here is my current code:
// Title
var title = section.AddParagraph("Photos");
title.Format.SpaceAfter = Unit.FromPoint(9);
title.Format.SpaceBefore = Unit.FromPoint(9);
title.Format.Font.Bold = true;
title.Format.Font.Color = new Color(33, 37, 41);
title.Format.Font.Name = "Segoe UI";
title.Format.Font.Size = Unit.FromPoint(11);
title.Format.Borders.Bottom = new Border()
{
Color = new Color(222, 226, 230),
Style = BorderStyle.Single
};
// listview
foreach (var photo in _photos)
{
var photoDocument = _documentService.Get(photo.DocumentId);
var textFrame = section.AddTextFrame();
textFrame.AddImage("base64:" + Convert.ToBase64String(photoDocument.ThumbnailBinaryData));
paragraph = textFrame.AddParagraph(photo.Filename);
paragraph.Format.Alignment = ParagraphAlignment.Center;
if (photo.CreatedOn != null)
{
var dateValue = (DateTimeOffset) photo.CreatedOn;
paragraph.AddLineBreak();
paragraph.AddText(dateValue.ToString("yyyy-MM-dd"));
}
}
The issue with this approach is that Migradoc is placing the photos vertically and the text basically on top of each other (see image below).
Question
How can I make it so that the images go from left-to-right until there is no longer any room in which case it starts to wrap on the next line?
Update
I'm able to get the photos to line up left-to-right, but without any text using:
var paragraph = section.AddParagraph();
foreach (var photo in _photos)
{
var photoDocument = _documentService.Get(photo.DocumentId);
paragraph.AddImage("base64:" + Convert.ToBase64String(photoDocument.ThumbnailBinaryData));
}
However, I still haven't figured out how to set the text below the image and have the images appear on the same line.
Update #2
After spending a long time thinking about this, ultimately what I did was create a table, calculate the maximum number of columns that can fit on the page, and then dynamically add columns as I go. I'm not sure if this is the right solution or not, but it works for me:
if (_photos.Any())
{
var columnWidth = Unit.FromInch(1.75);
var maximumColumns = (int)Math.Floor((11.5 - pageSetup.LeftMargin.Inch - pageSetup.RightMargin.Inch) / columnWidth.Inch); // 11.5 (page width) - 0.25 (left margin) - 0.25 (right margin) divided by columnWidth
var table = section.AddTable();
table.Style = "Table";
if (maximumColumns > photos.Count())
{
columnWidth = Unit.FromInch(Math.Floor((11.5 - pageSetup.LeftMargin.Inch - pageSetup.RightMargin.Inch) / photos.Count()));
maximumColumns = photos.Count();
}
Enumerable.Range(1, maximumColumns).ForEach(i => table.AddColumn(columnWidth));
var row = table.AddRow();
var columnIndex = 0;
foreach (var photo in _photos)
{
if (columnIndex == maximumColumns - 1)
{
row = table.AddRow();
columnIndex = 0;
}
else
{
columnIndex++;
}
var photoDocument = _documentService.Get(photo.DocumentId);
var photoParagraph = row.Cells[columnIndex].AddParagraph();
photoParagraph.AddImage("base64:" + Convert.ToBase64String(photoDocument.ThumbnailBinaryData));
photoParagraph.AddLineBreak();
photoParagraph.AddText(photo.Filename);
photoParagraph.Format.Alignment = ParagraphAlignment.Center;
if (photo.CreatedOn != null)
{
var dateValue = (DateTimeOffset)photo.CreatedOn;
photoParagraph.AddLineBreak();
photoParagraph.AddText(dateValue.ToString("yyyy-MM-dd"));
}
}
}
Related
I try crossout row in table. Line must be over all cells in center vertical.
So I add new shape to cell, like this:
var cell = node as Cell;
var width = cell.CellFormat.Width;
var line = new Shape(args.Document, ShapeType.Line);
line.Width = width;
line.HorizontalAlignment = HorizontalAlignment.Center;
line.RelativeVerticalPosition = RelativeVerticalPosition.Paragraph;
line.Top = 5;
line.BehindText = true;
line.WrapType = WrapType.None;
line.StrokeColor = Color.Black;
line.Stroke.LineStyle = ShapeLineStyle.Single;
line.StrokeWeight = 1;
_builder.MoveTo(cell.LastParagraph);
_builder.InsertNode(line);
But this work only when I have single text line in cell, if is two or more line text, my crossout line is not center:
How fix it?
Maybe is other solution for crossout whole row?
You can build logic on the following code that inserts a full-width Line Shape at the middle of each Cell in Table.
Document doc = new Document("E:\\temp\\in.docx");
DocumentBuilder builder = new DocumentBuilder(doc);
LayoutCollector collector = new LayoutCollector(doc);
LayoutEnumerator enumerator = new LayoutEnumerator(doc);
foreach (Table table in doc.FirstSection.Body.Tables)
{
foreach (Row row in table.Rows)
{
foreach (Cell cell in row.Cells)
{
enumerator.Current = collector.GetEntity(cell.FirstParagraph);
while (enumerator.Type != LayoutEntityType.Cell)
{
enumerator.MoveParent();
}
double top = enumerator.Rectangle.Top + (enumerator.Rectangle.Height / 2);
double left = enumerator.Rectangle.Left;
double width = enumerator.Rectangle.Width;
builder.MoveTo(table.NextSibling);
Shape line = builder.InsertShape(ShapeType.Line, width, 0);
line.Top = top;
line.Left = left;
line.RelativeHorizontalPosition = RelativeHorizontalPosition.Page;
line.RelativeVerticalPosition = RelativeVerticalPosition.Page;
line.BehindText = true;
line.WrapType = WrapType.None;
line.StrokeColor = Color.Blue;
line.Stroke.LineStyle = ShapeLineStyle.Single;
line.StrokeWeight = 1;
}
}
}
doc.Save("E:\\temp\\19.1.docx");
Hope, this help. I work with Aspose as Developer Evangelist.
I'm making with Visual Studio 2013 Coded UI Tests for the WPF-application of my company.
In that application we have - per example - a mask with the RadGridView from Telerik which contains a list with all users.
Because the application is still in development, i want to create generic methods.
In case of the mask with the userlist, i want to call a method, which i give the control, the cellId and the cellValue.
Coded UI recognizes the following hierarchy:
WpfTable - WpfRow - WpfText
Further i cannot see all user entries in the grid, which means, i have to scroll down while searching.
So i have programmed the following method:
private void SearchRowAndCell(WpfTable table, string cellId, string cellValue)
{
int n = 0;
int maxIndex = table.RowCount;
while (n < maxIndex)
{
var row = new WpfRow(table);
var rowIdForSearch = "Row_" + n;
row.SearchProperties.Add("AutomationId", rowIdForSearch);
UITestControlCollection foundRows = row.FindMatchingControls();
if (foundRows.Count > 0)
{
row = (WpfRow)foundRows[0];
var cell = new WpfCell(row);
var cellIdForSearch = "Cell_" + n + "_" + cellId;
cell.SearchProperties.Add("AutomationId", cellIdForSearch);
UITestControlCollection foundCells = cell.FindMatchingControls();
if (foundCells.Count > 0)
{
cell = (WpfCell)foundCells[0];
var text = new WpfText(cell);
text.SearchProperties.Add("DisplayText", cellValue);
UITestControlCollection foundTexts = text.FindMatchingControls();
if (foundTexts.Count > 0)
{
text = (WpfText)foundTexts[0];
Mouse.DoubleClick(row, new Point(10, 10));
n = maxIndex;
}
else
{
Keyboard.SendKeys(row, "{Down}", ModifierKeys.None);
}
}
}
n++;
}
}
Now my problem is, that i get an exception when i try to get the WpfText control because "DisplayText" is not a search property.
When i try "Value" instaed of "DisplayText" i find nothing.
Which search property do i need to find the WpfText with the selected value?
Thanks in advance!
you can try using the following :
WpfTable t = new WpfTable();
t.FindFirstCellWithValue(cellValue);
the 'FindFirstCellWithValue()' method is a wpfTable built in method
I guess you can just iterate over the cell contents and find the WpfText and then use string compare:
var cellContents = cell.GetChildren();
if (cellContents == null || cellContents.Count < 0) continue;
foreach (var content in cellContents)
{
WpfText wpfText = content as WpfText;
if (wpfText != null && wpfText.DisplayText.Equals(cellValue, StringComparison.OrdinalIgnoreCase))
{
Mouse.DoubleClick(row, new Point(10, 10));
}
}
I have a large form I'm transforming into a PDF. The first 1/6th of it looks like this:
http://i.imgur.com/y4pO8Th.png
The number of entered fields however, varies from 1 to 20 per section, and I need to be able to make this document break pages intelligently. My plan was to originally draw the tables piece by piece and just manage the Y-coordinate by grabbing the number of rows in all previous tables. This worked, but falls apart when I get to a page break, and I start needing some semi-complicated logic to make it work, and it's the kind of logic that gets messier and messier with each additional table added.
My second plan was to reproduce the table structure of the HTML document in the PDF, which I manage to do successfully...
private void DrawPDF()
{
Document tDoc = new Document();
MigraDoc.DocumentObjectModel.Style style = tDoc.Styles["Normal"];
style.Font.Name = tPdfFont;
style.Font.Size = 10;
Section tSec = tDoc.AddSection();
MigraDoc.DocumentObjectModel.Tables.Table masterTable = new MigraDoc.DocumentObjectModel.Tables.Table();
masterTable = tSec.AddTable();
masterTable.Borders.Visible = false;
Column leftColumn = masterTable.AddColumn("365pt");
Column spacer = masterTable.AddColumn("10pt");
Column rightColumn = masterTable.AddColumn("365pt");
Row tFS = masterTable.AddRow();
Cell tCell = tFS.Cells[0];
//
// Farm Assets Column
//
{
MigraDoc.DocumentObjectModel.Tables.Table tAssetsTable = new MigraDoc.DocumentObjectModel.Tables.Table();
tAssetsTable.Borders.Visible = false;
Column tColumn = tAssetsTable.AddColumn("365pt");
tCell.Elements.Add(tAssetsTable);
//
// Current Farm Assets
//
for (int i = 0; i < 10; i++) // Drawn 10 times to force it to draw over the 1st page.
{
Section thisSection = tDoc.AddSection();
Row tAssetsRow = tAssetsTable.AddRow();
Cell tAssetsCell = tAssetsRow.Cells[0];
MigraDoc.DocumentObjectModel.Tables.Table table = new MigraDoc.DocumentObjectModel.Tables.Table();
table = thisSection.AddTable();
table.Borders.Width = 0.2;
table.Rows.LeftIndent = 0;
Column columnData = table.AddColumn("295pt");
columnData.Borders.Left.Visible = false;
Column columnValue = table.AddColumn("70pt");
Row rowA = table.AddRow();
rowA.Shading.Color = Color.FromRgbColor((byte)255, Color.Parse("0xa2a2d2"));
rowA.Cells[0].AddParagraph("CURRENT FARM ASSETS");
rowA.Cells[1].AddParagraph("$ Value");
rowA.Cells[1].Format.Alignment = ParagraphAlignment.Right;
Row row1 = table.AddRow();
row1.Borders.Bottom.Visible = false;
row1.Cells[0].AddParagraph("Cash: Savings: ($" + MP.FormFinancialStatement.CurrentStaticAssets.Savings + ") Checking: ($" + MP.FormFinancialStatement.CurrentStaticAssets.Checking + ")");
row1.Cells[1].AddParagraph(MP.FormFinancialStatement.CurrentStaticAssets.CashTotal);
row1.Cells[1].Format.Alignment = ParagraphAlignment.Right;
Row row2 = table.AddRow();
row2.Borders.Bottom.Visible = false;
row2.Cells[0].AddParagraph("Invest: Time Cret $" + MP.FormFinancialStatement.CurrentStaticAssets.TimeCret + " Other: $" + MP.FormFinancialStatement.CurrentStaticAssets.OtherInvestments + "");
row2.Cells[1].AddParagraph(MP.FormFinancialStatement.CurrentStaticAssets.InvestTotal);
row2.Cells[1].Format.Alignment = ParagraphAlignment.Right;
Row row3 = table.AddRow();
row3.Borders.Bottom.Visible = false;
row3.Cells[0].AddParagraph("Replacement Account");
row3.Cells[1].AddParagraph(MP.FormFinancialStatement.CurrentStaticAssets.ReplacementAccount);
row3.Cells[1].Format.Alignment = ParagraphAlignment.Right;
Row row4 = table.AddRow();
row4.Cells[0].AddParagraph("Accouts and Notes Recievable");
row4.Cells[1].AddParagraph(MP.FormFinancialStatement.CurrentStaticAssets.AccountsNotesReceivable);
row4.Cells[1].Format.Alignment = ParagraphAlignment.Right;
MigraDoc.DocumentObjectModel.Tables.Table clone = (MigraDoc.DocumentObjectModel.Tables.Table)table.Clone();
tAssetsCell.Elements.Add(clone);
}
}
MigraDoc.Rendering.DocumentRenderer docRenderer = new DocumentRenderer(tDoc);
docRenderer.PrepareDocument();
docRenderer.RenderObject(gfx, 30, 85, "740pt", masterTable);
}
But alas, this does not actually break pages correctly. I tried sectioning off each individual table, hoping that'd do page break magic, but it does not.
How can I structure this to allow for good page breaks?
You can use the KeepWith property of the table rows to keep blocks together on one page. Only use this for chunks that will surely fit on one page.
See also:
https://stackoverflow.com/a/6831048/1015447
https://stackoverflow.com/a/1327228/1015447
I am trying to generate a telerik report table dynamically. Actually, after lots of efforts besides surfing the web I have come to the following code which works fine but not presenting the desired output. Here is the code:
private void table1_ItemDataBinding(object sender, EventArgs e)
{
// Get data and bind it to the table
var processingTable = (sender as Telerik.Reporting.Processing.Table);
var data = GenerateTable(DomainClass.GetCurrentAsset());
if (processingTable != null) processingTable.DataSource = data;
// Better clear table before binding
table1.ColumnGroups.Clear();
table1.Body.Columns.Clear();
table1.Body.Rows.Clear();
HtmlTextBox txtGroup;
HtmlTextBox txtTable;
//int rowIndex = 0;
for (int i = 0; i <= data.Columns.Count - 1; i++)
{
var tableGroupColumn = new TableGroup();
table1.ColumnGroups.Add(item: tableGroupColumn);
txtGroup = new HtmlTextBox
{
Size = new SizeU(Unit.Inch(2.1), Unit.Inch(0.3)),
Value = data.Columns[i].ColumnName,
Style =
{
BorderStyle = { Default = BorderType.Solid },
BorderColor = { Default = Color.Black }
},
};
tableGroupColumn.ReportItem = txtGroup;
txtTable = new HtmlTextBox()
{
Size = new SizeU(Unit.Inch(2.2), Unit.Inch(0.3)),
Value = "=Fields." + data.Columns[i].ColumnName,
//Value = data.Rows[rowIndex][i].ToString(),
Style =
{
BorderStyle = { Default = BorderType.Solid },
BorderColor = { Default = Color.Black }
}
};
table1.Body.SetCellContent(0, columnIndex: i, item: txtTable);
//rowIndex++;
table1.Items.AddRange(items: new ReportItemBase[] { txtTable, txtGroup });
}
}
Here is a picture of DataTable which contains the table data:
Finally, the current out put which is of course not what needed:
So the problem is; The Account_Price column does not display the Prices, though retrieved from the data store and can be seen in the data table picture above.
I have traced line by line of the code and could find out the prices as tracing the code. But I have no idea why they are not shown in the browser.
Hope any one could help me,
Thank you very much
Apparently, this is a newer issue with the later Telerik reporting.
I found the answer in the Telerik forums here:
http://www.telerik.com/community/forums/reporting/telerik-reporting/incorrect-dynamic-table-columns.aspx
Basically, you need to assign a unique name the TableGroup column:
TableGroup tableGroupColumn = new TableGroup();
//add this :
tableGroupColumn.Name = i.ToString();
I have created a web part with a button that once clicked generates a word document containing the list item values of particular list. I want to be able to change the margins for the document (top, bottom margins), but I am unsure as to how to proceed. Can anyone shed some light on how to achieve this?
So far the code I have is a as follows:
void GenerateBadges_Click(object sender, EventArgs e)
{
string Title = null;
string jobTitle = null;
WordprocessingDocument document = WordprocessingDocument.Create(#"C:\sample-
badges.docx", WordprocessingDocumentType.Document);
MainDocumentPart mainDocumenPart = document.AddMainDocumentPart();
mainDocumenPart.Document = new Document();
Body documentBody = new Body();
mainDocumenPart.Document.Append(documentBody);
SPWeb web = SPContext.Current.Web;
SPList list = web.Lists["SampleList"];
SPListItemCollection collListItems = list.Items;
//getting the internal name for the Title and JobTitle fields of the list
string jobTitleField = collListItems.Fields["JobTitle"].InternalName;
string titleField = collListItems.Fields["Title"].InternalName;
//adding a table to the document
//creating a properties object to add border to the table (wNo border will be required)
Table table = new Table();
TableProperties tblProps = new TableProperties();
TableBorders tblBorders = new TableBorders();
tblBorders.TopBorder = new TopBorder();
tblBorders.TopBorder.Val = new EnumValue<BorderValues>(BorderValues.Single);
tblBorders.BottomBorder = new BottomBorder();
tblBorders.BottomBorder.Val = new EnumValue<BorderValues>(BorderValues.Single);
tblBorders.RightBorder = new RightBorder();
tblBorders.RightBorder.Val = new EnumValue<BorderValues>(BorderValues.Single);
tblBorders.LeftBorder = new LeftBorder();
tblBorders.LeftBorder.Val = new EnumValue<BorderValues>(BorderValues.Single);
tblBorders.InsideHorizontalBorder = new InsideHorizontalBorder();
tblBorders.InsideHorizontalBorder.Val = BorderValues.Single;
tblBorders.InsideVerticalBorder = new InsideVerticalBorder();
tblBorders.InsideVerticalBorder.Val = BorderValues.Single;
tblProps.Append(tblBorders);
table.Append(tblProps);
int x = collListItems.Count;
//creatin the table rows/cells
for (int i = 0; (i * 2) < x; i++)
{
TableRow row = new TableRow();
// get the indexes for left and right cells as pairs (i.e. 0 + 1, 2 + 3, 4 + 5 etc)
int leftIndexer = i * 2;
int rightIndexer = (i * 2) + 1;
if (leftIndexer == x)
{
break;
}
//getting the values from the list for the left table cell
Title = collListItems[leftIndexer][titleField].ToString();
jobTitle = collListItems[leftIndexer][jobTitleField].ToString();
// attach content to row as cell
row.Append(new TableCell(new Paragraph(new Run(new Text(Title)))));
// get right cell contents, if there is a value for this index
if (rightIndexer < x)
{
//getting the values from the list for the right cell
Title = collListItems[rightIndexer][titleField].ToString();
jobTitle = collListItems[rightIndexer][jobTitleField].ToString();
// attach to table row as right cell
row.Append(new TableCell(new Paragraph(new Run(new Text(Title)))));
}
// attach row to table
table.Append(row);
}
//add the table to the document - table needs to be wired into the for each loop above
documentBody.Append(table);
//Saving/Disposing of the created word Document
document.MainDocumentPart.Document.Save();
document.Dispose();
The key part to be able to change the page margins is to firs t create a "PageMagrin" object and then a "SectionProperties" object. Lastly we need to append the page margin object in to the section properties object. Lastly append the section properties object to a body object.
Additionally it is very useful to create a word document, then save it as .xml. Then open it in notepad, notepad++ or even visual studio 2010. This displays all of th elements that make up the word document with this you are then able to determine which parts or elements of the document you need to modify.
Below is the actual code used:
//setting the page margins go here
PageMargin pageMargins = new PageMargin();
pageMargins.Left = 600;
pageMargins.Right = 600;
pageMargins.Bottom = 500;
pageMargins.Top = 500;
//pageMargins.Header = 1500; //not needed for now
//pageMargins.Footer = 1500; //not needed for now
//Important needed to access properties (sections) to set values for all elements.
SectionProperties sectionProps = new SectionProperties();
sectionProps.Append(pageMargins);
documentBody.Append(sectionProps);
Hope this helps others, I had a tough time figuring this out
It is a bit hard to tell what exactly you want to do - the following links contain details and source code for page margins, headers, footers etc.:
http://msdn.microsoft.com/en-us/library/ee355228%28office.12%29.aspx
http://sharepointweblog.blogspot.com/2009/08/wordprocessingml-insert-page-number-in.html
IF the above is not what you asked please provide more details on what you achieve...