using Aspose Words to replace page numbers with barcodes - c#

this may be a silly question but I cannot work out an answer to it and after a day I am turning to the community at large for help...
I am using Aspose for Word (C# or .Net) and I am trying to replace the generated page numbering for barcode images of my own creation. I can use fonts to do it currently but I have found they are less reliable with my barcode reader and thus need to be able to read the value from the page numbering and replace it with an image of my own creation.
So really I need to find the numbering container, read the value in it and replace it. Once I have that creating the barcode and inserting it is easy.
Can anyone help?
The current method (sorry its messy but i keep trying new things):
internal static void SetFooters(ref Document doc)
{
doc.FirstSection.HeadersFooters.LinkToPrevious(false);
var builder = new DocumentBuilder(doc);
builder.MoveToDocumentStart();
Section currentSection = builder.CurrentSection;
PageSetup pageSetup = currentSection.PageSetup;
int totalPages = doc.PageCount;
int j = 1;
foreach (Section sect in doc.Sections)
{
//Loop through all headers/footers
foreach (HeaderFooter hf in sect.HeadersFooters)
{
if (
hf.HeaderFooterType == HeaderFooterType.FooterPrimary || hf.HeaderFooterType == HeaderFooterType.FooterEven || hf.HeaderFooterType == HeaderFooterType.FooterFirst)
{
builder.MoveToHeaderFooter(hf.HeaderFooterType);
Field page = builder.InsertField("PAGE");
builder.Document.UpdatePageLayout();
try
{
page.Update();
}
catch { }
int pageNumber = j;
if (int.TryParse(page.Result, out pageNumber))
{ j++; }
// Remove PAGE field.
page.Remove();
builder.Write(string.Format("{0}/{1}", pageNumber, totalPages));
}
}
}
}

HeaderFooter is a section-level node and can only be a child of Section. The page field inside the header/footer returns the latest updated value and it will be same value for all pages of a section.
In your case, I suggest you to insert text-box at the top/bottom of each page and inset the desired contents in it. Following code example inserts the text-box on each page of document and insert page field and some text in it. Hope this helps you.
public static void InsertTextBoxAtEachPage()
{
string filePathIn = MyDir + #"input.docx";
string filePathOut = MyDir + #"output.docx";
Document doc = new Document(filePathIn);
DocumentBuilder builder = new DocumentBuilder(doc);
LayoutCollector collector = new LayoutCollector(doc);
int pageIndex = 1;
foreach (Section section in doc.Sections)
{
NodeCollection paragraphs = section.Body.GetChildNodes(NodeType.Paragraph, true);
foreach (Paragraph para in paragraphs)
{
if (collector.GetStartPageIndex(para) == pageIndex)
{
builder.MoveToParagraph(paragraphs.IndexOf(para), 0);
builder.StartBookmark("BM_Page" + pageIndex);
builder.EndBookmark("BM_Page" + pageIndex);
pageIndex++;
}
}
}
collector = new LayoutCollector(doc);
LayoutEnumerator layoutEnumerator = new LayoutEnumerator(doc);
const int PageRelativeY = 0;
const int PageRelativeX = 0;
foreach (Bookmark bookmark in doc.Range.Bookmarks)
{
if (bookmark.Name.StartsWith("BM_"))
{
Paragraph para = (Paragraph)bookmark.BookmarkStart.ParentNode;
Shape textbox = new Shape(doc, Aspose.Words.Drawing.ShapeType.TextBox);
textbox.Top = PageRelativeY;
textbox.Left = PageRelativeX;
int currentPageNumber = collector.GetStartPageIndex(para);
string barcodeString = string.Format("page {0} of {1}", currentPageNumber, doc.PageCount);
string barcodeEncodedString = "some barcode string";
Paragraph paragraph = new Paragraph(doc);
ParagraphFormat paragraphFormat = paragraph.ParagraphFormat;
paragraphFormat.Alignment = ParagraphAlignment.Center;
Aspose.Words.Style paragraphStyle = paragraphFormat.Style;
Aspose.Words.Font font = paragraphStyle.Font;
font.Name = "Tahoma";
font.Size = 12;
paragraph.AppendChild(new Run(doc, barcodeEncodedString));
textbox.AppendChild(paragraph);
paragraph = new Paragraph(doc);
paragraphFormat = paragraph.ParagraphFormat;
paragraphFormat.Alignment = ParagraphAlignment.Center;
paragraphStyle = paragraphFormat.Style;
font = paragraphStyle.Font;
font.Name = "Arial";
font.Size = 10;
paragraph.AppendChild(new Run(doc, barcodeString));
textbox.AppendChild(paragraph);
//Set the width height according to your requirements
textbox.Width = doc.FirstSection.PageSetup.PageWidth;
textbox.Height = 50;
textbox.BehindText = false;
para.AppendChild(textbox);
textbox.RelativeHorizontalPosition = Aspose.Words.Drawing.RelativeHorizontalPosition.Page;
textbox.RelativeVerticalPosition = Aspose.Words.Drawing.RelativeVerticalPosition.Page;
bool isInCell = bookmark.BookmarkStart.GetAncestor(NodeType.Cell) != null;
if (isInCell)
{
var renderObject = collector.GetEntity(bookmark.BookmarkStart);
layoutEnumerator.Current = renderObject;
layoutEnumerator.MoveParent(LayoutEntityType.Cell);
RectangleF location = layoutEnumerator.Rectangle;
textbox.Top = PageRelativeY - location.Y;
textbox.Left = PageRelativeX - location.X;
}
}
}
doc.Save(filePathOut, SaveFormat.Docx);
}
I work with Aspose as Developer evangelist.

Related

How to make the bottom of each column in alignment for columns page layout

I'm using ColumnDocumentRenderer to draw content in two columns.
Below are the codes.
public void ManipulatePdf(string dest)
{
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
Document doc = new Document(pdfDoc, PageSize.A4);
doc.SetMargins(55f, 55f, 45f, 55f);
var interval = 20f;
var columnWidth = (doc.GetPdfDocument().GetDefaultPageSize().GetWidth() - 110 - interval) / 2;
var pageHeight = doc.GetPdfDocument().GetDefaultPageSize().GetHeight() - 110;
var baseText = "We have seen too many reports, too many words, too many good intentions, too many families torn apart, and too many excruciatingly painful deaths to see yet more delays in taking collective action.";
var pTitle = new Paragraph(baseText);
pTitle.SetFontSize(20);
pTitle.SetTextAlignment(iText.Layout.Properties.TextAlignment.JUSTIFIED);
doc.Add(pTitle);
var currentYLine = doc.GetRenderer().GetCurrentArea().GetBBox().GetTop();
Rectangle[] columns = {
new Rectangle(55, 55,
columnWidth,
currentYLine - 55),
new Rectangle(55 + columnWidth + interval, 55,
columnWidth,
currentYLine - 55) };
doc.SetRenderer(new ColumnDocumentRenderer(doc, columns));
for (int i = 1; i <= 12; i++)
{
var text = baseText;
for (int j = 1; j <= i; j++)
{
text += " Additional Text. ";
}
var p = new Paragraph(text);
p.SetTextAlignment(iText.Layout.Properties.TextAlignment.JUSTIFIED);
doc.Add(p);
if (i == 6)
{
var tp = new Paragraph("Introduction");
tp.SetFontSize(20);
tp.SetMarginTop(50);
tp.SetTextAlignment(iText.Layout.Properties.TextAlignment.JUSTIFIED);
doc.Add(tp);
}
}
doc.Close();
}
Below is the created PDF:
Please take a look at the red line in this screenshot, my question is that how to make bottom of two columns alignment in a straight line?
Thanks & Regards.
As discussed in the comments to the original question, algorithm to auto-align the bottom lines does not exist in iText and defining its behavior turned out to be tricky even for the person asking the question.
However, it is possible to calculate the difference between the vertical positions of the bottom lines when the document is being rendered and then you can correct the content and create another document with proper layout.
Here is the example implementation which analyzes the positions of the content when it's being drawn on the document:
class CustomColumnDocumentRenderer : ColumnDocumentRenderer {
public CustomColumnDocumentRenderer(Document document, Rectangle[] columns) : base(document, columns) {
}
public CustomColumnDocumentRenderer(Document document, bool immediateFlush, Rectangle[] columns) : base(document, immediateFlush, columns) {
}
IDictionary<int, float> leftColumnBottom = new Dictionary<int, float>();
IDictionary<int, float> rightColumnBottom = new Dictionary<int, float>();
protected override void FlushSingleRenderer(IRenderer resultRenderer) {
TraverseRecursively(resultRenderer, leftColumnBottom, rightColumnBottom);
base.FlushSingleRenderer(resultRenderer);
}
void TraverseRecursively(IRenderer child, IDictionary<int, float> leftColumnBottom, IDictionary<int, float> rightColumnBottom) {
if (child is LineRenderer) {
int page = child.GetOccupiedArea().GetPageNumber();
if (!leftColumnBottom.ContainsKey(page)) {
leftColumnBottom[page] = 1000;
}
if (!rightColumnBottom.ContainsKey(page)) {
rightColumnBottom[page] = 1000;
}
bool isLeftColumn = !(child.GetOccupiedArea().GetBBox().GetX() > PageSize.A4.GetWidth() / 2);
if (isLeftColumn) {
leftColumnBottom[page] =
Math.Min(leftColumnBottom[page], child.GetOccupiedArea().GetBBox().GetBottom());
} else {
rightColumnBottom[page] = Math.Min(rightColumnBottom[page],
child.GetOccupiedArea().GetBBox().GetBottom());
}
} else {
foreach (IRenderer ownChild in (child is ParagraphRenderer ? (((ParagraphRenderer)child).GetLines().Cast<IRenderer>()) : child.GetChildRenderers())) {
TraverseRecursively(ownChild, leftColumnBottom, rightColumnBottom);
}
}
}
public List<float> getDiffs() {
List<float> ans = new List<float>();
foreach (int pageNum in leftColumnBottom.Keys) {
ans.Add(leftColumnBottom[pageNum] - rightColumnBottom[pageNum]);
}
return ans;
}
}
To use it, make sure to pass the customized document renderer to the Document instance:
CustomColumnDocumentRenderer renderer = new CustomColumnDocumentRenderer(doc, true, columns);
doc.SetRenderer(renderer);
Then you can get page-by-page diffs:
Console.WriteLine(renderer.getDiffs()[0]);

Having problems getting PDF document from text file formatted correctly with GemBox

I am attempting to convert a text file to a pdf using GemBox. I can get the text imported correctly but the font type and size aren't being applied and the sentence spacing seems to be doubled.
This is what I have so far:
public static void CreateDoc(string ebillpath)
{
using (var sr = new StreamReader(ebillpath))
{
var doc = new DocumentModel();
doc.DefaultCharacterFormat.Size = 10;
doc.DefaultCharacterFormat.FontName = "Courier New";
var section = new Section(doc);
doc.Sections.Add(section);
string line;
var clearedtop = false;
while ((line = sr.ReadLine()) != null)
{
if (string.IsNullOrEmpty(line) && !clearedtop)
{
continue;
}
clearedtop = true;
Paragraph paragraph2 = new Paragraph(doc, new Run(doc, line));
section.Blocks.Add(paragraph2);
}
PageSetup pageSetup = new PageSetup(); // section.PageSetup;
var pm = new PageMargins();
pm.Bottom = 36;
pm.Top = 36;
pm.Right = 36;
pm.Left = 36;
pageSetup.PageMargins = pm;
doc.Save(#"d:\temp\test.pdf");
}
}
This text file uses spaces to format the text correctly so I need to set the font to Courier New.
This is an example of what the text file looks like with correct formatting:
And this is what it comes out to look like in pdf form:
Each line seems to be doubled and the font isn't being applied.
Any suggestions?
Try this:
public static void CreateDoc(string ebillpath)
{
DocumentModel doc = new DocumentModel();
CharacterFormat charFormat = doc.DefaultCharacterFormat;
charFormat.Size = 10;
charFormat.FontName = "Courier New";
ParagraphFormat parFormat = doc.DefaultParagraphFormat;
parFormat.SpaceAfter = 0;
parFormat.LineSpacing = 1;
// It seems you want to skip first line with 'clearedtop'.
// So maybe you could just use this instead.
string text = string.Concat(File.ReadLines(ebillpath).Skip(1));
doc.Content.LoadText(text);
Section section = doc.Sections[0];
PageMargins margins = section.PageSetup.PageMargins;
margins.Bottom = 36;
margins.Top = 36;
margins.Right = 36;
margins.Left = 36;
doc.Save(#"d:\temp\test.pdf");
}
I hope it helps.

Remove blank (empty) space from PDF template after flattenning an empty AcroField using iTextSharp, c#

I am filling a PDF template that contains AcroFields using iTextSharp via Windows Forms application.
The data is filled via interface with multiple comboBoxes as the following:
string template = path1 + #"\Template1.pdf";
PdfReader pdfreader = new PdfReader(template);
StringBuilder sb = new StringBuilder();
foreach (KeyValuePair<string, iTextSharp.text.pdf.AcroFields.Item> de in
pdfreader.AcroFields.Fields)
{
sb.Append(de.Key.ToString() + Environment.NewLine);
}
string newPDF = path + #"\"name".pdf";
PdfStamper pdfstamper = new PdfStamper(pdfreader, new FileStream(newPDF,
FileMode.Create));
AcroFields pdffields = pdfstamper.AcroFields;
pdffields.SetField("ProductText", product.SelectedItem.ToString());
pdffields.SetField("indexText", index.SelectedItem.ToString());
.
.
.
pdfstamper.FormFlattening = true;
pdfstamper.Close();
The result in the PDF is as the following image:
But the question is: When the Grade is not selected (as shown in the previous image), I can remove the whole row of the Grade, but this will leave a blank empty space in the PDF as shown in the following image:
What are the possible ways to remove the empty blank space of the flattened empty fields? is it by shifting up the content that follows the space? Thanks.
The OP indicated in a comment to the question
I can handle the labels issue, but how can I shift the fields below?
Thus, the following code deals only with the fields.
Move up all fields below the empty field
So let's assume you check the value you have for a field before setting the field to it. If in case of an empty value you do not set the field value but instead call a method like the following to move up every field below that field:
void MoveUp(PdfStamper stamper, String fieldName, int pageNumber)
{
AcroFields fields = stamper.AcroFields;
IList<AcroFields.FieldPosition> positions = fields.GetFieldPositions(fieldName);
foreach (AcroFields.FieldPosition position in positions)
{
if (position.page == pageNumber)
{
IList<float> fieldYsBelowField = new List<float>();
PdfDictionary pageDict = stamper.Reader.GetPageN(pageNumber);
PdfArray annots = pageDict.GetAsArray(PdfName.ANNOTS);
for (int i = 0; i < annots.Size; i++)
{
PdfDictionary annot = annots.GetAsDict(i);
PdfArray rect = annot.GetAsArray(PdfName.RECT);
if (((PdfNumber)rect[1]).FloatValue < position.position.Bottom)
{
fieldYsBelowField.Add(((PdfNumber)rect[1]).FloatValue);
}
}
if (fieldYsBelowField.Count > 0)
{
float offset = position.position.Bottom - fieldYsBelowField.Max();
for (int i = 0; i < annots.Size; i++)
{
PdfDictionary annot = annots.GetAsDict(i);
PdfArray rect = annot.GetAsArray(PdfName.RECT);
if (((PdfNumber)rect[1]).FloatValue < position.position.Bottom)
{
rect[1] = new PdfNumber(((PdfNumber)rect[1]).FloatValue + offset);
rect[3] = new PdfNumber(((PdfNumber)rect[3]).FloatValue + offset);
}
}
}
}
}
}
you'll get what you want.
E.g. if for some single page form the following code
AcroFields fields = stamper.AcroFields;
fields.SetField("Product", "Product1");
fields.SetField("Index", "XQAA-0000-000");
fields.SetField("Quality", "USP,");
fields.SetField("Grade", "SomeGrade");
fields.SetField("Customer", "Customer1");
fields.SetField("Market", "England, Germany");
results in
and
AcroFields fields = stamper.AcroFields;
fields.SetField("Product", "Product1");
fields.SetField("Index", "XQAA-0000-000");
fields.SetField("Quality", "USP,");
fields.SetField("Customer", "Customer1");
fields.SetField("Market", "England, Germany");
results in
then
AcroFields fields = stamper.AcroFields;
fields.SetField("Product", "Product1");
fields.SetField("Index", "XQAA-0000-000");
fields.SetField("Quality", "USP,");
MoveUp(stamper, "Grade", 1);
fields.SetField("Customer", "Customer1");
fields.SetField("Market", "England, Germany");
results in
Move up only one field
In comments to the question the OP asked
Can I specify the fields to be shifted rather than shifting all below fields? [...] Let's say I want only Customer1 to be shifted to the position of SomeGrade.
A helper routine for this is even simpler:
void MoveUp(PdfStamper stamper, String fieldName, String moveFieldName, int pageNumber)
{
AcroFields fields = stamper.AcroFields;
IList<AcroFields.FieldPosition> positions = fields.GetFieldPositions(fieldName);
foreach (AcroFields.FieldPosition position in positions)
{
if (position.page == pageNumber)
{
Item moveFieldItem = fields.GetFieldItem(moveFieldName);
for (int i = 0; i < moveFieldItem.Size; i++)
{
if (moveFieldItem.GetPage(i) == pageNumber)
{
PdfDictionary annot = moveFieldItem.GetWidget(i);
PdfArray rect = annot.GetAsArray(PdfName.RECT);
float offset = position.position.Bottom - ((PdfNumber)rect[1]).FloatValue;
rect[1] = new PdfNumber(((PdfNumber)rect[1]).FloatValue + offset);
rect[3] = new PdfNumber(((PdfNumber)rect[3]).FloatValue + offset);
break;
}
}
}
}
}
Using this method,
AcroFields fields = stamper.AcroFields;
fields.SetField("Product", "Product1");
fields.SetField("Index", "XQAA-0000-000");
fields.SetField("Quality", "USP,");
MoveUp(stamper, "Grade", "Customer", 1);
fields.SetField("Customer", "Customer1");
fields.SetField("Market", "England, Germany");
results in

Get the positions of unique elements in a string[]

I have an xml file that I am accessing to create a report of time spent on a project. I'm returning the unique dates to a label created dynamically on a winform and would like to compile the time spent on a project for each unique date. I have been able to return all of the projects under each date or only one project. Currently I'm stuck on only returning one project. Can anyone please help me?? This is what the data should look like if it's correct.
04/11/15
26820 2.25
27111 8.00
04/12/15
26820 8.00
04/13/15
01det 4.33
26820 1.33
27225 4.25
etc.
This is how I'm retrieving the data
string[] weekDateString = elementDateWeekstring();
string[] uniqueDates = null;
string[] weeklyJobNumber = elementJobNumWeek();
string[] weeklyTicks = elementTicksWeek();
This is how I'm getting the unique dates.
IEnumerable<string> distinctWeekDateIE = weekDateString.Distinct();
foreach (string d in distinctWeekDateIE)
{
uniqueDates = distinctWeekDateIE.ToArray();
}
And this is how I'm creating the labels.
try
{
int dateCount;
dateCount = uniqueDates.Length;
Label[] lblDate = new Label[dateCount];
int htDate = 1;
int padDate = 10;
for (int i = 0; i < dateCount; i++ )
{
lblDate[i] = new Label();
lblDate[i].Name = uniqueDates[i].Trim('\r');
lblDate[i].Text = uniqueDates[i];
lblDate[i].TabIndex = i;
lblDate[i].Bounds = new Rectangle(18, 275 + padDate + htDate, 75, 22);
targetForm.Controls.Add(lblDate[i]);
htDate += 22;
foreach (string x in uniqueDates)
{
int[] posJobNumber;
posJobNumber = weekDateString.Select((b, a) => b == uniqueDates[i].ToString() ? a : -1).Where(a => a != -1).ToArray();
for (int pjn = 0; pjn < posJobNumber.Length; pjn++)
{
if (x.Equals(lblDate[i].Text))
{
Label lblJobNum = new Label();
int htJobNum = 1;
int padJobNum = 10;
lblJobNum.Name = weeklyJobNumber[i];
lblJobNum.Text = weeklyJobNumber[i];
lblJobNum.Bounds = new Rectangle(100, 295 + padJobNum + htJobNum, 75, 22);
targetForm.Controls.Add(lblJobNum);
htJobNum += 22;
htDate += 22;
padJobNum += 22;
}
}
}
}
}
I've been stuck on this for about 3 months. Is there anyone that can describe to me why I'm not able to properly retrieve the job numbers that are associated with a particular date. I don't believe that these are specifically being returned as dates. Just a string that looks like a date.
I really appreciate any help I can get. I'm just completely baffled. Thank you for any responses in advance. I truly appreciate the assistance.
EDIT: #Sayka - Here is the xml sample.
<?xml version="1.0" encoding="utf-8"?>
<Form1>
<Name Key="4/21/2014 6:51:17 AM">
<Date>4/21/2014</Date>
<JobNum>26820</JobNum>
<RevNum>00000</RevNum>
<Task>Modeling Secondary</Task>
<Start>06:51 AM</Start>
<End>04:27 PM</End>
<TotalTime>345945089017</TotalTime>
</Name>
<Name Key="4/22/2014 5:44:22 AM">
<Date>4/22/2014</Date>
<JobNum>26820</JobNum>
<RevNum>00000</RevNum>
<Task>Modeling Secondary</Task>
<Start>05:44 AM</Start>
<End>06:56 AM</End>
<TotalTime>43514201221</TotalTime>
</Name>
<Name Key="4/22/2014 6:57:02 AM">
<Date>4/22/2014</Date>
<JobNum>02e-n-g</JobNum>
<RevNum>00000</RevNum>
<Task>NET Eng</Task>
<Start>06:57 AM</Start>
<End>07:16 AM</End>
<TotalTime>11706118875</TotalTime>
</Name>
....
</Form1>
This is how I'm getting the information out of the xml file and returning a string[].
public static string[] elementDateWeekstring()
{
//string datetxtWeek = "";
XmlDocument xmldoc = new XmlDocument();
fileExistsWeek(xmldoc);
XmlNodeList nodeDate = xmldoc.GetElementsByTagName("Date");
int countTicks = 0;
string[] dateTxtWeek = new string[nodeDate.Count];
for (int i = 0; i < nodeDate.Count; i++)
{
dateTxtWeek[i] = nodeDate[i].InnerText;
countTicks++;
}
return dateTxtWeek;
}
Job number and Ticks are returned in a similar fashion. I've been able to reuse these snippets throught out the code. This is a one dimensional xml file?? It will always return a position for a jobnumber that equates to a date or Ticks. I will never have more or less of any one element.
You can use Linq-to-XML to parse the XML file, and then use Linq-to-objects to group (and order) the data by job date and order each group by job name.
The code to parse the XML file is like so:
var doc = XDocument.Load(filename);
var jobs = doc.Descendants("Name");
// Extract the date, job number, and total time from each "Name" element.:
var data = jobs.Select(job => new
{
Date = (DateTime)job.Element("Date"),
Number = (string)job.Element("JobNum"),
Duration = TimeSpan.FromTicks((long)job.Element("TotalTime"))
});
The code to group and order the jobs by date and order the groups by job name is:
var result =
data.GroupBy(job => job.Date).OrderBy(g => g.Key)
.Select(g => new
{
Date = g.Key,
Jobs = g.OrderBy(item => item.Number)
});
Then you can access the data by iterating over each group in result and then iterate over each job in the group, like so:
foreach (var jobsOnDate in result)
{
Console.WriteLine("{0:d}", jobsOnDate.Date);
foreach (var job in jobsOnDate.Jobs)
Console.WriteLine(" {0} {1:hh\\:mm}", job.Number, job.Duration);
}
Putting this all together in a sample compilable console application (substitute the filename for the XML file as appropriate):
using System;
using System.Linq;
using System.Xml.Linq;
namespace ConsoleApplication2
{
class Program
{
private static void Main()
{
string filename = #"d:\test\test.xml"; // Substitute your own filename here.
// Open XML file and get a collection of each "Name" element.
var doc = XDocument.Load(filename);
var jobs = doc.Descendants("Name");
// Extract the date, job number, and total time from each "Name" element.:
var data = jobs.Select(job => new
{
Date = (DateTime)job.Element("Date"),
Number = (string)job.Element("JobNum"),
Duration = TimeSpan.FromTicks((long)job.Element("TotalTime"))
});
// Group the jobs by date, and order the groups by job name:
var result =
data.GroupBy(job => job.Date).OrderBy(g => g.Key)
.Select(g => new
{
Date = g.Key,
Jobs = g.OrderBy(item => item.Number)
});
// Print out the results:
foreach (var jobsOnDate in result)
{
Console.WriteLine("{0:d}", jobsOnDate.Date);
foreach (var job in jobsOnDate.Jobs)
Console.WriteLine(" {0} {1:hh\\:mm}", job.Number, job.Duration);
}
}
}
}
The output is like this
Create a new project
Set form size bigger.
Apply these codes.
Set the location for your XML file.
Namespaces
using System.Xml;
using System.IO;
Form Code
public partial class Form1 : Form
{
const string XML_FILE_NAME = "D:\\emps.txt";
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
prepareDataGrid();
List<JOBS> jobsList = prepareXML(XML_FILE_NAME);
for (int i = 0; i < jobsList.Count; i++)
{
addDateRow(jobsList[i].jobDate.ToString("M'/'d'/'yyyy"));
for (int j = 0; j < jobsList[i].jobDetailsList.Count; j++)
dgv.Rows.Add(new string[] {
jobsList[i].jobDetailsList[j].JobNumber,
jobsList[i].jobDetailsList[j].JobHours
});
}
}
DataGridView dgv;
void prepareDataGrid()
{
dgv = new DataGridView();
dgv.BackgroundColor = Color.White;
dgv.GridColor = Color.White;
dgv.DefaultCellStyle.SelectionBackColor = Color.White;
dgv.DefaultCellStyle.SelectionForeColor = Color.Black;
dgv.DefaultCellStyle.ForeColor = Color.Black;
dgv.DefaultCellStyle.BackColor = Color.White;
dgv.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
dgv.Width = 600;
dgv.Dock = DockStyle.Left;
this.BackColor = Color.White;
dgv.Columns.Add("Col1", "Col1");
dgv.Columns.Add("Col2", "Col2");
dgv.Columns[0].Width = 110;
dgv.Columns[1].Width = 40;
dgv.DefaultCellStyle.Font = new System.Drawing.Font("Segoe UI", 10);
dgv.RowHeadersVisible = dgv.ColumnHeadersVisible = false;
dgv.AllowUserToAddRows =
dgv.AllowUserToDeleteRows =
dgv.AllowUserToOrderColumns =
dgv.AllowUserToResizeColumns =
dgv.AllowUserToResizeRows =
!(dgv.ReadOnly = true);
Controls.Add(dgv);
}
void addJobRow(string jobNum, string jobHours)
{
dgv.Rows.Add(new string[] {jobNum, jobHours });
}
void addDateRow(string date)
{
dgv.Rows.Add(new string[] { date, ""});
dgv.Rows[dgv.Rows.Count - 1].DefaultCellStyle.SelectionForeColor =
dgv.Rows[dgv.Rows.Count - 1].DefaultCellStyle.ForeColor = Color.Firebrick;
dgv.Rows[dgv.Rows.Count - 1].DefaultCellStyle.Font = new Font("Segoe UI Light", 13.5F);
dgv.Rows[dgv.Rows.Count - 1].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleLeft;
dgv.Rows[dgv.Rows.Count - 1].Height = 25;
}
List<JOBS> prepareXML(string fileName)
{
string xmlContent = "";
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
using (StreamReader sr = new StreamReader(fs)) xmlContent = sr.ReadToEnd();
XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlContent);
List<JOBS> jobsList = new List<JOBS>();
XmlNode form1Node = doc.ChildNodes[1];
for (int i = 0; i < form1Node.ChildNodes.Count; i++)
{
XmlNode dateNode = form1Node.ChildNodes[i].ChildNodes[0].ChildNodes[0],
jobNumNode = form1Node.ChildNodes[i].ChildNodes[1].ChildNodes[0],
timeTicksNode = form1Node.ChildNodes[i].ChildNodes[6].ChildNodes[0];
bool foundDate = false;
for (int j = 0; j < jobsList.Count; j++) if (jobsList[j].compareDate(dateNode.Value))
{
jobsList[j].addJob(jobNumNode.Value, Math.Round(TimeSpan.FromTicks(
(long)Convert.ToDouble(timeTicksNode.Value)).TotalHours, 2).ToString());
foundDate = true;
break;
}
if (!foundDate)
{
JOBS job = new JOBS(dateNode.Value);
string jbnum = jobNumNode.Value;
string tbtck = timeTicksNode.Value;
long tktk = Convert.ToInt64(tbtck);
double tkdb = TimeSpan.FromTicks(tktk).TotalHours;
job.addJob(jobNumNode.Value, Math.Round(TimeSpan.FromTicks(
Convert.ToInt64(timeTicksNode.Value)).TotalHours, 2).ToString());
jobsList.Add(job);
}
}
jobsList.OrderByDescending(x => x.jobDate);
return jobsList;
}
class JOBS
{
public DateTime jobDate;
public List<JobDetails> jobDetailsList = new List<JobDetails>();
public void addJob(string jobNumber, string jobHours)
{
jobDetailsList.Add(new JobDetails() { JobHours = jobHours, JobNumber = jobNumber });
}
public JOBS(string dateString)
{
jobDate = getDateFromString(dateString);
}
public JOBS() { }
public bool compareDate(string dateString)
{
return getDateFromString(dateString) == jobDate;
}
private DateTime getDateFromString(string dateString)
{
string[] vals = dateString.Split('/');
return new DateTime(Convert.ToInt32(vals[2]), Convert.ToInt32(vals[0]), Convert.ToInt32(vals[1]));
}
}
class JobDetails
{
public string JobNumber { get; set; }
public string JobHours { get; set; }
}
}

Span Two Columns When Dynamically Adding Controls

I have an ASP.NET web application. This application renders out glossary type items, similar to the following:
This goes through all the letters in the alphabet for items. I am rendering this out and appending it directly to a Controls collection in a Server Control using the following:
List<char> alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray().ToList();
foreach (char c in alpha)
{
Label lblAlphaCharacter = new Label();
lblAlphaCharacter.Font.Size = 24;
lblAlphaCharacter.Font.Bold = true;
lblAlphaCharacter.Text = c.ToString(CultureInfo.InvariantCulture);
Controls.Add(lblAlphaCharacter);
Controls.Add(new LiteralControl("<p>"));
FilterOnAlphaCharacter(this, Page, c);
Controls.Add(new LiteralControl("<p>"));
}
private static void FilterOnAlphaCharacter(Control control, Page page, char character)
{
foreach (List<Things> item in items)
{
string title = item.Title;
string description = item.Definition;
HyperLink link = new HyperLink();
link.Text = title;
control.Controls.Add(link);
Label lblDescription = new Label();
lblDescription.Text = string.Format(" - {0}", description);
control.Controls.Add(lblDescription);
}
}
}
I am trying to, depending on the content, equally split this, so that it looks like this:
This can have different amounts of items. So in reality, there could be 25 entries under "A", and perhaps 1 under "Z". The above is just an example, it goes through all letters A-Z. The expected result would be based on the amount of content, it would equally split between two columns. I have to do this server-side (I was thinking using Table or HtmlTable and related objects).
Howe would you implement a solution for splitting the content equally in a Table or the likes (sort of indifferent on approach).
try this:
//it shows the number of line that inserting during the process
private int _inserteditemCount;
//its number of items in each column
private int _itemsCount;
//line height use for determine paragraph line height
private const string Lineheight = "30px";
protected void Page_Load(object sender, EventArgs e)
{
_inserteditemCount = 0;
var alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
//you can do this query in data access layer
var listCountcount = new Thingsclass().GetThings().Count;
//Count of rows on dictionary + number of leters
_itemsCount = (listCountcount + alpha.Count()) / 2;
var leftdiv = new HtmlGenericControl("div");
var rightdiv = new HtmlGenericControl("div");
//you can change this styles
leftdiv.Style.Add("display", "inline-block");
leftdiv.Style.Add("width", "50%");
leftdiv.Style.Add("float", "Left");
rightdiv.Style.Add("display", "inline-block");
rightdiv.Style.Add("float", "right");
rightdiv.Style.Add("width", "50%");
foreach (var c in alpha)
{
var lblAlphaCharacter = new Label();
lblAlphaCharacter.Font.Size = 24;
lblAlphaCharacter.Font.Bold = true;
lblAlphaCharacter.Text = c.ToString(CultureInfo.InvariantCulture);
var control = _inserteditemCount <= _itemsCount ? leftdiv : rightdiv;
var paragraph = new HtmlGenericControl("p");
paragraph.Style.Add("line-height", Lineheight);
paragraph.Controls.Add(lblAlphaCharacter);
control.Controls.Add(paragraph);
FilterOnAlphaCharacter(leftdiv, rightdiv, c.ToString());
_inserteditemCount++;
}
Panel1.Controls.Add(leftdiv);
Panel1.Controls.Add(rightdiv);
}
private void FilterOnAlphaCharacter(Control leftctr, Control rightctr, string character)
{
//you can do this query in data access layer
var items = new Thingsclass().GetThings().Where(c => c.chara.ToLower().Equals(character.ToLower()));
foreach (var item in items)
{
var paragraph = new HtmlGenericControl("p");
paragraph.Style.Add("line-height", Lineheight);
var control = _inserteditemCount <= _itemsCount ? leftctr : rightctr;
var title = item.Title;
var description = item.Description;
var link = new HyperLink { Text = title };
paragraph.Controls.Add(link);
var lblDescription = new Label { Text = string.Format(" - {0}", description) };
paragraph.Controls.Add(lblDescription);
_inserteditemCount++;
control.Controls.Add(paragraph);
}
}

Categories